Merge pull request #1718 from Benjamin-Davies/cheneer

Implement Chen Notation for ER-diagrams
This commit is contained in:
PlantUML 2024-03-21 22:30:28 +01:00 committed by GitHub
commit 4afe3ef7fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 5466 additions and 6 deletions

View File

@ -48,6 +48,7 @@ import net.sourceforge.plantuml.activitydiagram3.ActivityDiagramFactory3;
import net.sourceforge.plantuml.api.PSystemFactory;
import net.sourceforge.plantuml.board.BoardDiagramFactory;
import net.sourceforge.plantuml.bpm.BpmDiagramFactory;
import net.sourceforge.plantuml.cheneer.ChenEerDiagramFactory;
import net.sourceforge.plantuml.chronology.ChronologyDiagramFactory;
import net.sourceforge.plantuml.classdiagram.ClassDiagramFactory;
import net.sourceforge.plantuml.core.Diagram;
@ -277,6 +278,8 @@ public class PSystemBuilder {
factories.add(new HclDiagramFactory());
factories.add(new PSystemEbnfFactory());
factories.add(new PSystemRegexFactory());
factories.add(new ChenEerDiagramFactory());
}
private boolean isOk(Diagram ps) {

View File

@ -62,6 +62,8 @@ public enum LeafType {
PORTIN, PORTOUT,
CHEN_ENTITY, CHEN_RELATIONSHIP, CHEN_ATTRIBUTE, CHEN_CIRCLE,
STILL_UNKNOWN;
public static LeafType getLeafType(String type) {

View File

@ -0,0 +1,95 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer;
import java.util.Map;
import java.util.Stack;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.classdiagram.AbstractEntityDiagram;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.skin.UmlDiagramType;
public class ChenEerDiagram extends AbstractEntityDiagram {
public ChenEerDiagram(UmlSource source, Map<String, String> skinParam) {
super(source, UmlDiagramType.CHEN_EER, skinParam);
}
private final Stack<Entity> ownerStack = new Stack<Entity>();
/**
* Pushes the owner of the following attributes.
*
* @see #peekOwner()
* @param group the entity that owns the following attributes
*/
public void pushOwner(Entity group) {
ownerStack.push(group);
}
/**
* Pops an attribute owner from the stack. See also {@link #peekOwner()}.
*
* @see #peekOwner()
* @return true if an owner was popped, false if the stack was empty
*/
public boolean popOwner() {
if (ownerStack.isEmpty()) {
return false;
}
ownerStack.pop();
return true;
}
/**
* Returns the owner of the current attribute.
*
* <p>
* This is used to link attributes based on their lexical position (how they
* appear in sources) without nesting the entities (like how packages are
* done). It is for this reason that we can't use CucaDiagram.getCurrentGroup,
* as that method nests the entities.
*
* @return the owner of the current attribute, or null if there is no owner
*/
public Entity peekOwner() {
if (ownerStack.isEmpty()) {
return null;
}
return ownerStack.peek();
}
}

View File

@ -0,0 +1,78 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer;
import java.util.List;
import java.util.Map;
import net.sourceforge.plantuml.AbstractPSystem;
import net.sourceforge.plantuml.cheneer.command.CommandAssociate;
import net.sourceforge.plantuml.cheneer.command.CommandCreateAttribute;
import net.sourceforge.plantuml.cheneer.command.CommandCreateEntity;
import net.sourceforge.plantuml.cheneer.command.CommandEndGroup;
import net.sourceforge.plantuml.cheneer.command.CommandMultiSubclass;
import net.sourceforge.plantuml.cheneer.command.CommandSimpleSubclass;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommonCommands;
import net.sourceforge.plantuml.command.PSystemCommandFactory;
import net.sourceforge.plantuml.core.DiagramType;
import net.sourceforge.plantuml.core.UmlSource;
public class ChenEerDiagramFactory extends PSystemCommandFactory {
public ChenEerDiagramFactory() {
super(DiagramType.CHEN_EER);
}
@Override
protected void initCommandsList(List<Command> cmds) {
CommonCommands.addTitleCommands(cmds);
CommonCommands.addCommonCommands2(cmds);
cmds.add(new CommandCreateEntity());
cmds.add(new CommandCreateAttribute());
cmds.add(new CommandAssociate());
cmds.add(new CommandEndGroup());
cmds.add(new CommandSimpleSubclass());
cmds.add(new CommandMultiSubclass());
}
@Override
public AbstractPSystem createEmptyDiagram(UmlSource source, Map<String, String> skinParam) {
return new ChenEerDiagram(source, skinParam);
}
}

View File

@ -0,0 +1,108 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.abel.LinkArg;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.decoration.LinkDecor;
import net.sourceforge.plantuml.decoration.LinkType;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.plasma.Quark;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexOptional;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandAssociate extends SingleLineCommand2<ChenEerDiagram> {
public CommandAssociate() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandCreateEntity.class.getName(), RegexLeaf.start(), //
new RegexLeaf("NAME1", "([\\w-]+)"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("PARTICIPATION", "([-=])"), //
new RegexOptional( //
new RegexLeaf("CARDINALITY", "(\\w+|\\(\\w+,[%s]*\\w+\\))")), //
new RegexLeaf("PARTICIPATION2", "([-=])"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("NAME2", "([\\w-]+)"), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg)
throws NoSuchColorException {
final String name1 = diagram.cleanId(arg.get("NAME1", 0));
final String name2 = diagram.cleanId(arg.get("NAME2", 0));
final boolean isDouble = arg.get("PARTICIPATION", 0).equals("=");
final String cardinality = arg.get("CARDINALITY", 0);
final Quark<Entity> quark1 = diagram.quarkInContext(true, name1);
final Entity entity1 = quark1.getData();
if (entity1 == null) {
return CommandExecutionResult.error("No such entity: " + name1);
}
final Quark<Entity> quark2 = diagram.quarkInContext(true, name2);
final Entity entity2 = quark2.getData();
if (entity2 == null) {
return CommandExecutionResult.error("No such entity: " + name2);
}
LinkType linkType = new LinkType(LinkDecor.NONE, LinkDecor.NONE);
if (isDouble) {
linkType = linkType.goBold();
}
final Link link = new Link(diagram.getEntityFactory(), diagram.getCurrentStyleBuilder(), entity1, entity2,
linkType,
LinkArg.build(Display.getWithNewlines(cardinality), 3));
link.setPortMembers(diagram.getPortId(entity1.getName()), diagram.getPortId(entity2.getName()));
diagram.addLink(link);
return CommandExecutionResult.ok();
}
}

View File

@ -0,0 +1,126 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.LeafType;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.abel.LinkArg;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.decoration.LinkDecor;
import net.sourceforge.plantuml.decoration.LinkType;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.plasma.Quark;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexOptional;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.stereo.Stereotype;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandCreateAttribute extends SingleLineCommand2<ChenEerDiagram> {
public CommandCreateAttribute() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandCreateEntity.class.getName(), RegexLeaf.start(), //
RegexLeaf.spaceZeroOrMore(),
new RegexOptional( // Copied from CommandCreatePackageBlock
new RegexConcat( //
new RegexLeaf("DISPLAY", "[%g]([^%g]+)[%g]"), //
RegexLeaf.spaceOneOrMore(), //
new RegexLeaf("as"), //
RegexLeaf.spaceOneOrMore() //
)), //
new RegexLeaf("CODE", "([%pLN%s_.:]+)"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("STEREO", "(<<.*>>)?"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("COMPOSITE", "(\\{)?"), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg)
throws NoSuchColorException {
final Entity owner = diagram.peekOwner();
if (owner == null) {
return CommandExecutionResult.error("Attribute must be inside an entity, relationship or another attribute");
}
final LeafType type = LeafType.CHEN_ATTRIBUTE;
final String idShort = diagram.cleanId(arg.get("CODE", 0).trim());
final String id = owner.getName() + "/" + idShort;
final Quark<Entity> quark = diagram.quarkInContext(true, id);
String displayText = arg.get("DISPLAY", 0);
if (displayText == null)
displayText = idShort;
final String stereo = arg.get("STEREO", 0);
final boolean composite = arg.get("COMPOSITE", 0) != null;
Entity entity = quark.getData();
if (entity == null) {
final Display display = Display.getWithNewlines(displayText);
entity = diagram.reallyCreateLeaf(quark, display, type, null);
} else {
return CommandExecutionResult.error("Attribute already exists");
}
if (stereo != null) {
entity.setStereotype(Stereotype.build(stereo));
entity.setStereostyle(stereo);
}
final LinkType linkType = new LinkType(LinkDecor.NONE, LinkDecor.NONE);
final Link link = new Link(diagram.getEntityFactory(), diagram.getCurrentStyleBuilder(), entity, owner, linkType,
LinkArg.build(Display.NULL, 2));
diagram.addLink(link);
if (composite) {
diagram.pushOwner(entity);
}
return CommandExecutionResult.ok();
}
}

View File

@ -0,0 +1,123 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.LeafType;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.plasma.Quark;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexOptional;
import net.sourceforge.plantuml.regex.RegexOr;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.stereo.Stereotype;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandCreateEntity extends SingleLineCommand2<ChenEerDiagram> {
public CommandCreateEntity() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandCreateEntity.class.getName(), RegexLeaf.start(), //
new RegexLeaf("TYPE", "(entity|relationship)"), //
RegexLeaf.spaceOneOrMore(), //
new RegexOptional( // Copied from CommandCreatePackageBlock
new RegexConcat( //
new RegexLeaf("DISPLAY", "[%g]([^%g]+)[%g]"), //
RegexLeaf.spaceOneOrMore(), //
new RegexLeaf("as"), //
RegexLeaf.spaceOneOrMore() //
)), //
new RegexLeaf("CODE", "([%pLN_.]+)"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("STEREO", "(<<.+>>)?"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("\\{"), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg)
throws NoSuchColorException {
LeafType type;
switch (arg.get("TYPE", 0)) {
case "entity":
type = LeafType.CHEN_ENTITY;
break;
case "relationship":
type = LeafType.CHEN_RELATIONSHIP;
break;
default:
throw new IllegalStateException();
}
final String idShort = arg.get("CODE", 0);
final Quark<Entity> quark = diagram.quarkInContext(true, diagram.cleanId(idShort));
String displayText = arg.get("DISPLAY", 0);
if (displayText == null)
displayText = quark.getName();
final String stereo = arg.get("STEREO", 0);
Entity entity = quark.getData();
if (entity == null) {
Display display = Display.getWithNewlines(displayText);
entity = diagram.reallyCreateLeaf(quark, display, type, null);
} else {
if (entity.muteToType(type, null) == false)
return CommandExecutionResult.error("Bad name");
}
if (stereo != null) {
entity.setStereotype(Stereotype.build(stereo));
entity.setStereostyle(stereo);
}
diagram.pushOwner(entity);
return CommandExecutionResult.ok();
}
}

View File

@ -0,0 +1,67 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandEndGroup extends SingleLineCommand2<ChenEerDiagram> {
public CommandEndGroup() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandEndGroup.class.getName(), RegexLeaf.start(), //
new RegexLeaf("\\}"), //
RegexLeaf.end()); //
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg) {
if (!diagram.popOwner()) {
return CommandExecutionResult.error("Unbalanced brackets");
}
return CommandExecutionResult.ok();
}
}

View File

@ -0,0 +1,138 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.LeafType;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.abel.LinkArg;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.decoration.LinkDecor;
import net.sourceforge.plantuml.decoration.LinkType;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.plasma.Quark;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandMultiSubclass extends SingleLineCommand2<ChenEerDiagram> {
public CommandMultiSubclass() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandCreateEntity.class.getName(), RegexLeaf.start(), //
new RegexLeaf("SUPERCLASS", "([\\w-]+)"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("PARTICIPATION", "([-=])"), //
new RegexLeaf("(>)"), //
new RegexLeaf("PARTICIPATION2", "([-=])"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("SYMBOL", "([doU])"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("\\{"), //
new RegexLeaf("SUBCLASSES", "(.+)"), //
new RegexLeaf("\\}"), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg)
throws NoSuchColorException {
final String superclass = diagram.cleanId(arg.get("SUPERCLASS", 0));
final String subclasses = arg.get("SUBCLASSES", 0);
final List<String> subclassIds = Arrays.stream(subclasses.split(","))
.map(String::trim)
.map(diagram::cleanId)
.collect(Collectors.toList());
final boolean isDouble = arg.get("PARTICIPATION", 0).equals("=");
final String symbol = arg.get("SYMBOL", 0);
final Quark<Entity> centerQuark = diagram.quarkInContext(false, superclass + "/" + symbol + subclasses + "/center");
final Entity centerEntity = diagram.reallyCreateLeaf(centerQuark, Display.create(symbol), LeafType.CHEN_CIRCLE, null);
final Quark<Entity> superclassQuark = diagram.quarkInContext(true, superclass);
final Entity superclassEntity = superclassQuark.getData();
if (superclassEntity == null) {
return CommandExecutionResult.error("No such entity: " + superclass);
}
LinkType linkType = new LinkType(LinkDecor.NONE, LinkDecor.NONE);
if (isDouble) {
linkType = linkType.goBold();
}
if (symbol.equals("U")) {
linkType = linkType.withMiddleSuperset();
}
final Link link = new Link(diagram.getEntityFactory(), diagram.getCurrentStyleBuilder(), superclassEntity,
centerEntity,
linkType,
LinkArg.build(Display.NULL, 2));
link.setPortMembers(diagram.getPortId(superclassEntity.getName()), diagram.getPortId(centerEntity.getName()));
diagram.addLink(link);
for (String subclass : subclassIds) {
final Quark<Entity> subclassQuark = diagram.quarkInContext(true, subclass);
final Entity subclassEntity = subclassQuark.getData();
if (subclassEntity == null) {
return CommandExecutionResult.error("No such entity: " + subclass);
}
LinkType subclassLinkType = new LinkType(LinkDecor.NONE, LinkDecor.NONE);
if (!symbol.equals("U")) {
subclassLinkType = subclassLinkType.withMiddleSuperset();
}
final Link subclassLink = new Link(diagram.getEntityFactory(), diagram.getCurrentStyleBuilder(), centerEntity,
subclassEntity,
subclassLinkType,
LinkArg.build(Display.NULL, 3));
diagram.addLink(subclassLink);
}
return CommandExecutionResult.ok();
}
}

View File

@ -0,0 +1,111 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.cheneer.command;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.abel.LinkArg;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.decoration.LinkDecor;
import net.sourceforge.plantuml.decoration.LinkType;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.plasma.Quark;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandSimpleSubclass extends SingleLineCommand2<ChenEerDiagram> {
public CommandSimpleSubclass() {
super(getRegexConcat());
}
protected static IRegex getRegexConcat() {
return RegexConcat.build(CommandCreateEntity.class.getName(), RegexLeaf.start(), //
new RegexLeaf("NAME1", "([\\w-]+)"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("PARTICIPATION", "([-=])"), //
new RegexLeaf("DIRECTION", "([<>])"), //
new RegexLeaf("PARTICIPATION2", "([-=])"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf("NAME2", "([\\w-]+)"), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChenEerDiagram diagram, LineLocation location, RegexResult arg)
throws NoSuchColorException {
String name1 = diagram.cleanId(arg.get("NAME1", 0));
String name2 = diagram.cleanId(arg.get("NAME2", 0));
final boolean isDouble = arg.get("PARTICIPATION", 0).equals("=");
final boolean isSuperset = arg.get("DIRECTION", 0).equals(">");
final Quark<Entity> quark1 = diagram.quarkInContext(true, name1);
final Entity entity1 = quark1.getData();
if (entity1 == null) {
return CommandExecutionResult.error("No such entity: " + name1);
}
final Quark<Entity> quark2 = diagram.quarkInContext(true, name2);
final Entity entity2 = quark2.getData();
if (entity2 == null) {
return CommandExecutionResult.error("No such entity: " + name2);
}
LinkType linkType = new LinkType(LinkDecor.NONE, LinkDecor.NONE);
if (isDouble) {
linkType = linkType.goBold();
}
if (isSuperset) {
linkType = linkType.withMiddleSuperset();
} else {
linkType = linkType.withMiddleSubset();
}
final Link link = new Link(diagram.getEntityFactory(), diagram.getCurrentStyleBuilder(), entity1, entity2,
linkType,
LinkArg.build(Display.NULL, 3));
link.setPortMembers(diagram.getPortId(entity1.getName()), diagram.getPortId(entity2.getName()));
diagram.addLink(link);
return CommandExecutionResult.ok();
}
}

View File

@ -5,12 +5,12 @@
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
*
* If you like this project or if you find it useful, you can support us at:
*
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
@ -40,7 +40,7 @@ import net.sourceforge.plantuml.utils.StartUtils;
public enum DiagramType {
// ::remove folder when __HAXE__
UML, BPM, DITAA, DOT, PROJECT, JCCKIT, SALT, FLOW, CREOLE, MATH, LATEX, DEFINITION, GANTT, CHRONOLOGY, NW, MINDMAP,
WBS, WIRE, JSON, GIT, BOARD, YAML, HCL, EBNF, REGEX, FILES, UNKNOWN;
WBS, WIRE, JSON, GIT, BOARD, YAML, HCL, EBNF, REGEX, FILES, CHEN_EER, UNKNOWN;
static public DiagramType getTypeFromArobaseStart(String s) {
s = s.toLowerCase();
@ -130,6 +130,9 @@ public enum DiagramType {
if (StartUtils.startsWithSymbolAnd("startchronology", s))
return CHRONOLOGY;
if (StartUtils.startsWithSymbolAnd("startchen", s))
return CHEN_EER;
return UNKNOWN;
}
}

View File

@ -40,10 +40,11 @@ import net.sourceforge.plantuml.svek.extremity.MiddleCircleCircledMode;
import net.sourceforge.plantuml.svek.extremity.MiddleFactory;
import net.sourceforge.plantuml.svek.extremity.MiddleFactoryCircle;
import net.sourceforge.plantuml.svek.extremity.MiddleFactoryCircleCircled;
import net.sourceforge.plantuml.svek.extremity.MiddleFactorySubset;
public enum LinkMiddleDecor {
NONE, CIRCLE, CIRCLE_CIRCLED, CIRCLE_CIRCLED1, CIRCLE_CIRCLED2;
NONE, CIRCLE, CIRCLE_CIRCLED, CIRCLE_CIRCLED1, CIRCLE_CIRCLED2, SUBSET, SUPERSET;
public MiddleFactory getMiddleFactory(HColor backColor, HColor diagramBackColor) {
if (this == CIRCLE)
@ -57,6 +58,12 @@ public enum LinkMiddleDecor {
if (this == CIRCLE_CIRCLED2)
return new MiddleFactoryCircleCircled(MiddleCircleCircledMode.MODE2, backColor, diagramBackColor);
if (this == SUBSET)
return new MiddleFactorySubset(false);
if (this == SUPERSET)
return new MiddleFactorySubset(true);
throw new UnsupportedOperationException();
}

View File

@ -148,6 +148,14 @@ public class LinkType {
return new LinkType(decor1, decor2, LinkMiddleDecor.CIRCLE_CIRCLED2, linkStyle);
}
public LinkType withMiddleSubset() {
return new LinkType(decor1, decor2, LinkMiddleDecor.SUBSET, linkStyle);
}
public LinkType withMiddleSuperset() {
return new LinkType(decor1, decor2, LinkMiddleDecor.SUPERSET, linkStyle);
}
public LinkType getInvisible() {
return new LinkType(decor1, decor2, middleDecor, LinkStyle.INVISIBLE());
}

View File

@ -39,7 +39,7 @@ import net.sourceforge.plantuml.style.SName;
public enum UmlDiagramType {
SEQUENCE, STATE, CLASS, OBJECT, ACTIVITY, DESCRIPTION, COMPOSITE, FLOW, TIMING, BPM, NWDIAG, MINDMAP, WBS, WIRE,
HELP, GANTT, SALT, JSON, GIT, BOARD, YAML, HCL, EBNF, REGEX, FILES, CHRONOLOGY;
HELP, GANTT, SALT, JSON, GIT, BOARD, YAML, HCL, EBNF, REGEX, FILES, CHRONOLOGY, CHEN_EER;
public SName getStyleName() {
if (this == SEQUENCE)

View File

@ -64,6 +64,10 @@ public enum SName {
component, //
composite, //
robust, //
chenAttribute, //
chenEerDiagram, //
chenEntity, //
chenRelationship, //
concise, //
clock, //
componentDiagram, //

View File

@ -99,6 +99,10 @@ import net.sourceforge.plantuml.svek.image.EntityImageArcCircle;
import net.sourceforge.plantuml.svek.image.EntityImageAssociation;
import net.sourceforge.plantuml.svek.image.EntityImageAssociationPoint;
import net.sourceforge.plantuml.svek.image.EntityImageBranch;
import net.sourceforge.plantuml.svek.image.EntityImageChenAttribute;
import net.sourceforge.plantuml.svek.image.EntityImageChenCircle;
import net.sourceforge.plantuml.svek.image.EntityImageChenEntity;
import net.sourceforge.plantuml.svek.image.EntityImageChenRelationship;
import net.sourceforge.plantuml.svek.image.EntityImageCircleEnd;
import net.sourceforge.plantuml.svek.image.EntityImageCircleStart;
import net.sourceforge.plantuml.svek.image.EntityImageClass;
@ -254,6 +258,18 @@ public final class GeneralImageBuilder {
if (leaf.getLeafType() == LeafType.TIPS)
return new EntityImageTips(leaf, skinParam, bibliotekon, umlDiagramType);
if (leaf.getLeafType() == LeafType.CHEN_ENTITY)
return new EntityImageChenEntity(leaf, skinParam);
if (leaf.getLeafType() == LeafType.CHEN_RELATIONSHIP)
return new EntityImageChenRelationship(leaf, skinParam);
if (leaf.getLeafType() == LeafType.CHEN_ATTRIBUTE)
return new EntityImageChenAttribute(leaf, skinParam);
if (leaf.getLeafType() == LeafType.CHEN_CIRCLE)
return new EntityImageChenCircle(leaf, skinParam);
// TODO Clean
if (leaf.getLeafType() == LeafType.DOMAIN && leaf.getStereotype() != null
&& leaf.getStereotype().isMachineOrSpecification())

View File

@ -0,0 +1,52 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.extremity;
import net.sourceforge.plantuml.klimt.shape.UDrawable;
public class MiddleFactorySubset implements MiddleFactory {
private final boolean reverse;
public MiddleFactorySubset(boolean reverse) {
this.reverse = reverse;
}
public UDrawable createUDrawable(double angle) {
return new MiddleSubset(angle, reverse);
}
}

View File

@ -0,0 +1,82 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.extremity;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.geom.XPoint2D;
import net.sourceforge.plantuml.klimt.shape.UEllipse;
import net.sourceforge.plantuml.klimt.shape.ULine;
class MiddleSubset extends Extremity {
private final double radius = 6;
private final double length = 10;
private final double angle;
private final boolean reverse;
public MiddleSubset(double angle, boolean reverse) {
this.angle = angle;
this.reverse = reverse;
}
@Override
public XPoint2D somePoint() {
return null;
}
public void drawU(UGraphic ug) {
ug = ug.apply(UStroke.withThickness(1.5));
final double rotate = reverse ? -45 : 135;
ug.apply(new UTranslate(-radius, -radius)).draw(new UEllipse(2 * radius, 2 * radius, angle + rotate, 180));
final double sin = Math.sin(Math.toRadians(angle + rotate));
final double cos = Math.cos(Math.toRadians(angle + rotate));
ug.apply(new UTranslate(radius * cos, radius * -sin)).draw(new ULine(length * sin, length * cos));
ug.apply(new UTranslate(radius * -cos, radius * sin)).draw(new ULine(length * sin, length * cos));
}
@Override
public double getDecorationLength() {
return radius;
}
}

View File

@ -0,0 +1,177 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.image;
import java.util.Collections;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.klimt.UGroupType;
import net.sourceforge.plantuml.klimt.UShape;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.color.ColorType;
import net.sourceforge.plantuml.klimt.color.Colors;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.creole.CreoleMode;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.font.FontConfiguration;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.HorizontalAlignment;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.klimt.shape.TextBlockInEllipse;
import net.sourceforge.plantuml.klimt.shape.UEllipse;
import net.sourceforge.plantuml.style.ISkinParam;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignatureBasic;
import net.sourceforge.plantuml.svek.AbstractEntityImage;
import net.sourceforge.plantuml.svek.ShapeType;
import net.sourceforge.plantuml.url.Url;
public class EntityImageChenAttribute extends AbstractEntityImage {
final static private int MARGIN = 6;
final private boolean isKey;
final private boolean isMulti;
final private boolean isDerived;
final private TextBlock title;
final private Url url;
public EntityImageChenAttribute(Entity entity, ISkinParam skinParam) {
super(entity, skinParam);
isKey = hasStereotype("<<key>>");
isMulti = hasStereotype("<<multi>>");
isDerived = hasStereotype("<<derived>>");
FontConfiguration titleFontConfiguration = getStyleTitle(entity, skinParam)
.getFontConfiguration(getSkinParam().getIHtmlColorSet(), entity.getColors());
if (isKey) {
titleFontConfiguration = titleFontConfiguration.underline();
}
title = entity.getDisplay().create8(titleFontConfiguration, HorizontalAlignment.CENTER, skinParam, CreoleMode.FULL,
getStyle().wrapWidth());
url = entity.getUrl99();
}
private boolean hasStereotype(String stereotype) {
return getEntity().getStereotype() != null && getEntity().getStereotype().toString().contains(stereotype);
}
private Style getStyle() {
return getStyle(getEntity(), getSkinParam());
}
private static Style getStyle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenAttribute)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
private static Style getStyleTitle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenAttribute, SName.title)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
@Override
public ShapeType getShapeType() {
return ShapeType.OVAL;
}
@Override
public XDimension2D calculateDimension(StringBounder stringBounder) {
final TextBlockInEllipse ellipse = new TextBlockInEllipse(title, stringBounder);
return ellipse.calculateDimension(stringBounder);
}
@Override
public void drawU(UGraphic ug) {
ug.startGroup(Collections.singletonMap(UGroupType.ID, getEntity().getQuark().toStringPoint()));
if (url != null)
ug.startUrl(url);
final XDimension2D dimTotal = calculateDimension(ug.getStringBounder());
final XDimension2D dimTitle = title.calculateDimension(ug.getStringBounder());
UStroke stroke = getStyle().getStroke(getEntity().getColors());
if (isDerived) {
stroke = new UStroke(10, 10, stroke.getThickness());
}
ug = applyColor(ug);
ug = ug.apply(stroke);
ug.draw(getShape(dimTotal));
if (isMulti) {
ug.apply(new UTranslate(3, 3)).draw(getShape(dimTotal.delta(-6)));
}
final double xTitle = (dimTotal.getWidth() - dimTitle.getWidth()) / 2;
final double yTitle = MARGIN;
title.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
if (url != null)
ug.closeUrl();
ug.closeGroup();
}
final protected UGraphic applyColor(UGraphic ug) {
Colors colors = getEntity().getColors();
HColor border = colors.getColor(ColorType.LINE);
if (border == null)
border = getStyle().value(PName.LineColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(border);
HColor backcolor = colors.getColor(ColorType.BACK);
if (backcolor == null)
backcolor = getStyle().value(PName.BackGroundColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(backcolor.bg());
return ug;
}
private UShape getShape(XDimension2D dimTotal) {
return UEllipse.build(dimTotal.getWidth(), dimTotal.getHeight());
}
}

View File

@ -0,0 +1,175 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.image;
import java.util.Collections;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.klimt.UGroupType;
import net.sourceforge.plantuml.klimt.UShape;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.color.ColorType;
import net.sourceforge.plantuml.klimt.color.Colors;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.creole.CreoleMode;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.font.FontConfiguration;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.HorizontalAlignment;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.klimt.shape.TextBlockInEllipse;
import net.sourceforge.plantuml.klimt.shape.UEllipse;
import net.sourceforge.plantuml.style.ISkinParam;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignatureBasic;
import net.sourceforge.plantuml.svek.AbstractEntityImage;
import net.sourceforge.plantuml.svek.ShapeType;
import net.sourceforge.plantuml.url.Url;
public class EntityImageChenCircle extends AbstractEntityImage {
final static private int MARGIN = 6;
final private boolean isKey;
final private boolean isMulti;
final private boolean isDerived;
final private TextBlock title;
final private Url url;
public EntityImageChenCircle(Entity entity, ISkinParam skinParam) {
super(entity, skinParam);
isKey = hasStereotype("<<key>>");
isMulti = hasStereotype("<<multi>>");
isDerived = hasStereotype("<<derived>>");
FontConfiguration titleFontConfiguration = getStyleTitle(entity, skinParam)
.getFontConfiguration(getSkinParam().getIHtmlColorSet(), entity.getColors());
if (isKey) {
titleFontConfiguration = titleFontConfiguration.underline();
}
title = entity.getDisplay().create8(titleFontConfiguration, HorizontalAlignment.CENTER, skinParam, CreoleMode.FULL,
getStyle().wrapWidth());
url = entity.getUrl99();
}
private boolean hasStereotype(String stereotype) {
return getEntity().getStereotype() != null && getEntity().getStereotype().toString().contains(stereotype);
}
private Style getStyle() {
return getStyle(getEntity(), getSkinParam());
}
private static Style getStyle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.circle)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
private static Style getStyleTitle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.circle, SName.title)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
@Override
public ShapeType getShapeType() {
return ShapeType.OVAL;
}
@Override
public XDimension2D calculateDimension(StringBounder stringBounder) {
return new XDimension2D(25, 25);
}
@Override
public void drawU(UGraphic ug) {
ug.startGroup(Collections.singletonMap(UGroupType.ID, getEntity().getQuark().toStringPoint()));
if (url != null)
ug.startUrl(url);
final XDimension2D dimTotal = calculateDimension(ug.getStringBounder());
final XDimension2D dimTitle = title.calculateDimension(ug.getStringBounder());
UStroke stroke = getStyle().getStroke(getEntity().getColors());
if (isDerived) {
stroke = new UStroke(10, 10, stroke.getThickness());
}
ug = applyColor(ug);
ug = ug.apply(stroke);
ug.draw(getShape(dimTotal));
if (isMulti) {
ug.apply(new UTranslate(3, 3)).draw(getShape(dimTotal.delta(-6)));
}
final double xTitle = (dimTotal.getWidth() - dimTitle.getWidth()) / 2;
final double yTitle = (dimTotal.getHeight() - dimTitle.getHeight()) / 2;
title.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
if (url != null)
ug.closeUrl();
ug.closeGroup();
}
final protected UGraphic applyColor(UGraphic ug) {
Colors colors = getEntity().getColors();
HColor border = colors.getColor(ColorType.LINE);
if (border == null)
border = getStyle().value(PName.LineColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(border);
HColor backcolor = colors.getColor(ColorType.BACK);
if (backcolor == null)
backcolor = getStyle().value(PName.BackGroundColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(backcolor.bg());
return ug;
}
private UShape getShape(XDimension2D dimTotal) {
return UEllipse.build(dimTotal.getWidth(), dimTotal.getHeight());
}
}

View File

@ -0,0 +1,162 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.image;
import java.util.Collections;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.klimt.UGroupType;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.color.ColorType;
import net.sourceforge.plantuml.klimt.color.Colors;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.creole.CreoleMode;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.font.FontConfiguration;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.HorizontalAlignment;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.klimt.shape.URectangle;
import net.sourceforge.plantuml.style.ISkinParam;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignatureBasic;
import net.sourceforge.plantuml.svek.AbstractEntityImage;
import net.sourceforge.plantuml.svek.ShapeType;
import net.sourceforge.plantuml.url.Url;
public class EntityImageChenEntity extends AbstractEntityImage {
final private boolean isWeak;
final private TextBlock title;
final private Url url;
public EntityImageChenEntity(Entity entity, ISkinParam skinParam) {
super(entity, skinParam);
isWeak = hasStereotype("<<weak>>");
final FontConfiguration titleFontConfiguration = getStyleStateTitle(entity, skinParam)
.getFontConfiguration(getSkinParam().getIHtmlColorSet(), entity.getColors());
title = entity.getDisplay().create8(titleFontConfiguration, HorizontalAlignment.CENTER, skinParam, CreoleMode.FULL,
getStyleState().wrapWidth());
url = entity.getUrl99();
}
private boolean hasStereotype(String stereotype) {
return getEntity().getStereotype() != null && getEntity().getStereotype().toString().contains(stereotype);
}
private Style getStyleState() {
return getStyleState(getEntity(), getSkinParam());
}
private static Style getStyleState(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenEntity)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
private static Style getStyleStateTitle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenEntity, SName.title)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
@Override
public ShapeType getShapeType() {
return ShapeType.RECTANGLE;
}
@Override
public XDimension2D calculateDimension(StringBounder stringBounder) {
final XDimension2D dim = title.calculateDimension(stringBounder);
return dim.delta(MARGIN * 2 + 2 * MARGIN_LINE);
}
@Override
public void drawU(UGraphic ug) {
ug.startGroup(Collections.singletonMap(UGroupType.ID, getEntity().getQuark().toStringPoint()));
if (url != null)
ug.startUrl(url);
final XDimension2D dimTotal = calculateDimension(ug.getStringBounder());
final XDimension2D dimTitle = title.calculateDimension(ug.getStringBounder());
final UStroke stroke = getStyleState().getStroke(getEntity().getColors());
ug = applyColor(ug);
ug = ug.apply(stroke);
ug.draw(getShape(dimTotal));
if (isWeak) {
ug.apply(new UTranslate(3, 3)).draw(getShape(dimTotal.delta(-6)));
}
final double xTitle = (dimTotal.getWidth() - dimTitle.getWidth()) / 2;
final double yTitle = MARGIN + MARGIN_LINE;
title.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
if (url != null)
ug.closeUrl();
ug.closeGroup();
}
final protected UGraphic applyColor(UGraphic ug) {
Colors colors = getEntity().getColors();
HColor border = colors.getColor(ColorType.LINE);
if (border == null)
border = getStyleState().value(PName.LineColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(border);
HColor backcolor = colors.getColor(ColorType.BACK);
if (backcolor == null)
backcolor = getStyleState().value(PName.BackGroundColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(backcolor.bg());
return ug;
}
private URectangle getShape(XDimension2D dimTotal) {
return URectangle.build(dimTotal);
}
}

View File

@ -0,0 +1,178 @@
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.svek.image;
import java.util.Arrays;
import java.util.Collections;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.klimt.UGroupType;
import net.sourceforge.plantuml.klimt.UShape;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.color.ColorType;
import net.sourceforge.plantuml.klimt.color.Colors;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.creole.CreoleMode;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.font.FontConfiguration;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.HorizontalAlignment;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.geom.XPoint2D;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.klimt.shape.UPolygon;
import net.sourceforge.plantuml.style.ISkinParam;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignatureBasic;
import net.sourceforge.plantuml.svek.AbstractEntityImage;
import net.sourceforge.plantuml.svek.ShapeType;
import net.sourceforge.plantuml.url.Url;
public class EntityImageChenRelationship extends AbstractEntityImage {
final private boolean isIdentifying;
final private TextBlock title;
final private Url url;
public EntityImageChenRelationship(Entity entity, ISkinParam skinParam) {
super(entity, skinParam);
isIdentifying = hasStereotype("<<identifying>>");
final FontConfiguration titleFontConfiguration = getStyleTitle(entity, skinParam)
.getFontConfiguration(getSkinParam().getIHtmlColorSet(), entity.getColors());
title = entity.getDisplay().create8(titleFontConfiguration, HorizontalAlignment.CENTER, skinParam, CreoleMode.FULL,
getStyle().wrapWidth());
url = entity.getUrl99();
}
private boolean hasStereotype(String stereotype) {
return getEntity().getStereotype() != null && getEntity().getStereotype().toString().contains(stereotype);
}
private Style getStyle() {
return getStyle(getEntity(), getSkinParam());
}
private static Style getStyle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenRelationship)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
private static Style getStyleTitle(Entity group, ISkinParam skinParam) {
return StyleSignatureBasic.of(SName.root, SName.element, SName.chenEerDiagram, SName.chenRelationship, SName.title)
.withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder());
}
@Override
public ShapeType getShapeType() {
return ShapeType.DIAMOND;
}
@Override
public XDimension2D calculateDimension(StringBounder stringBounder) {
final XDimension2D dimTitle = title.calculateDimension(stringBounder);
// Fit a diamond with aspect ratio 2 around the text
// diagonal = dimTitle dot ([1, 2] / sqrt(5)) + 2 * MARGIN
// dimTotal = diagonal * [sqrt(5), sqrt(5) / 2]
final double diagonal = (dimTitle.getWidth() + 2 * dimTitle.getHeight()) / Math.sqrt(5) + 2 * MARGIN;
return new XDimension2D(diagonal * Math.sqrt(5), diagonal * Math.sqrt(5) / 2);
}
@Override
public void drawU(UGraphic ug) {
ug.startGroup(Collections.singletonMap(UGroupType.ID, getEntity().getQuark().toStringPoint()));
if (url != null)
ug.startUrl(url);
final XDimension2D dimTotal = calculateDimension(ug.getStringBounder());
final XDimension2D dimTitle = title.calculateDimension(ug.getStringBounder());
final UStroke stroke = getStyle().getStroke(getEntity().getColors());
ug = applyColor(ug);
ug = ug.apply(stroke);
ug.draw(getShape(dimTotal));
if (isIdentifying) {
ug.apply(new UTranslate(10, 5)).draw(getShape(dimTotal.delta(-20, -10)));
}
final double xTitle = (dimTotal.getWidth() - dimTitle.getWidth()) / 2;
final double yTitle = (dimTotal.getHeight() - dimTitle.getHeight()) / 2;
title.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
if (url != null)
ug.closeUrl();
ug.closeGroup();
}
final protected UGraphic applyColor(UGraphic ug) {
Colors colors = getEntity().getColors();
HColor border = colors.getColor(ColorType.LINE);
if (border == null)
border = getStyle().value(PName.LineColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(border);
HColor backcolor = colors.getColor(ColorType.BACK);
if (backcolor == null)
backcolor = getStyle().value(PName.BackGroundColor).asColor(getSkinParam().getIHtmlColorSet());
ug = ug.apply(backcolor.bg());
return ug;
}
private UShape getShape(XDimension2D dimTotal) {
final double width = dimTotal.getWidth();
final double height = dimTotal.getHeight();
final XPoint2D p1 = new XPoint2D(0, height / 2);
final XPoint2D p2 = new XPoint2D(width / 2, 0);
final XPoint2D p3 = new XPoint2D(width, height / 2);
final XPoint2D p4 = new XPoint2D(width / 2, height);
return new UPolygon(Arrays.asList(p1, p2, p3, p4));
}
}

View File

@ -0,0 +1,126 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
public class CommandAssociateTest {
private final Command<ChenEerDiagram> command = new CommandAssociate();
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parse() {
IRegex regex = CommandAssociate.getRegexConcat();
RegexResult matcher = regex.matcher("E1 -- E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("CARDINALITY", 0)).isNull();
}
@Test
void test_parseWithCompulsoryParticipation() {
IRegex regex = CommandAssociate.getRegexConcat();
RegexResult matcher = regex.matcher("E1 == E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("=");
assertThat(matcher.get("CARDINALITY", 0)).isNull();
}
@Test
void test_parseWithCardinality() {
IRegex regex = CommandAssociate.getRegexConcat();
RegexResult matcher = regex.matcher("E1 -N- E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("CARDINALITY", 0)).isEqualTo("N");
}
@Test
void test_parseCardinalityRange() {
IRegex regex = CommandAssociate.getRegexConcat();
RegexResult matcher = regex.matcher("E1 -(1, n)- E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("CARDINALITY", 0)).isEqualTo("(1, n)");
}
@Test
void test_execute() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 -- E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(1);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link.getLabel().toString()).isEqualTo("NULL");
}
@Test
void test_executeWithCompulsoryParticipation() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 == E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(1);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(2);
assertThat(link.getLabel().toString()).isEqualTo("NULL");
}
@Test
void test_executeWithCardinality() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 -N- E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(1);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link.getLabel().toString()).isEqualTo("[N]");
}
}

View File

@ -0,0 +1,173 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
public class CommandCreateAttributeTest {
private final Command<ChenEerDiagram> command = new CommandCreateAttribute();
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parse() {
IRegex regex = CommandCreateAttribute.getRegexConcat();
RegexResult matcher = regex.matcher("attr");
assertThat(matcher).isNotNull();
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("attr");
assertThat(matcher.get("STEREO", 0)).isNull();
assertThat(matcher.get("COMPOSITE", 0)).isNull();
}
@Test
void test_parseWithType() {
IRegex regex = CommandCreateAttribute.getRegexConcat();
RegexResult matcher = regex.matcher("attr : String");
assertThat(matcher).isNotNull();
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("attr : String");
assertThat(matcher.get("STEREO", 0)).isNull();
assertThat(matcher.get("COMPOSITE", 0)).isNull();
}
@Test
void test_parseComposite() {
IRegex regex = CommandCreateAttribute.getRegexConcat();
RegexResult matcher = regex.matcher("attr{");
assertThat(matcher).isNotNull();
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("attr");
assertThat(matcher.get("STEREO", 0)).isNull();
assertThat(matcher.get("COMPOSITE", 0)).isNotNull();
}
@Test
void test_parseWithDisplay() {
IRegex regex = CommandCreateAttribute.getRegexConcat();
RegexResult matcher = regex.matcher("\"My Attribute\" as attr");
assertThat(matcher).isNotNull();
assertThat(matcher.get("DISPLAY", 0)).isEqualTo("My Attribute");
assertThat(matcher.get("CODE", 0)).isEqualTo("attr");
assertThat(matcher.get("STEREO", 0)).isNull();
assertThat(matcher.get("COMPOSITE", 0)).isNull();
}
@Test
void test_parseWithStereo() {
IRegex regex = CommandCreateAttribute.getRegexConcat();
RegexResult matcher = regex.matcher("attr<<red>>");
assertThat(matcher).isNotNull();
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("attr");
assertThat(matcher.get("STEREO", 0)).isEqualTo("<<red>>");
assertThat(matcher.get("COMPOSITE", 0)).isNull();
}
@Test
void test_execute() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E {"));
BlocLines lines = BlocLines.singleString("\"Attribute\" as attr<<stereo>>");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Entity entity = diagram.quarkInContext(true, "E").getData();
assertThat(entity).isNotNull();
Entity attribute = diagram.quarkInContext(true, "E/attr").getData();
assertThat(attribute).isNotNull();
assertThat(attribute.getDisplay().toString()).isEqualTo("[Attribute]");
assertThat(attribute.getStereotype().toString()).isEqualTo("<<stereo>>");
assertThat(diagram.getLinks().size()).isEqualTo(1);
Link link = diagram.getLinks().get(0);
assertThat(link).isNotNull();
assertThat(link.getEntity1()).isSameAs(attribute);
assertThat(link.getEntity2()).isSameAs(entity);
}
@Test
void test_executeNested() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E {"));
CommandExecutionResult result1 = command.execute(diagram, BlocLines.singleString("attr1{"));
CommandExecutionResult result2 = command.execute(diagram, BlocLines.singleString("attr2"));
assertThat(result1).matches(CommandExecutionResult::isOk);
assertThat(result2).matches(CommandExecutionResult::isOk);
Entity entity = diagram.quarkInContext(true, "E").getData();
assertThat(entity).isNotNull();
Entity attribute1 = diagram.quarkInContext(true, "E/attr1").getData();
assertThat(attribute1).isNotNull();
assertThat(attribute1.getDisplay().toString()).isEqualTo("[attr1]");
Entity attribute2 = diagram.quarkInContext(true, "E/attr1/attr2").getData();
assertThat(attribute2).isNotNull();
assertThat(attribute2.getDisplay().toString()).isEqualTo("[attr2]");
assertThat(diagram.getLinks().size()).isEqualTo(2);
Link link1 = diagram.getLinks().get(0);
assertThat(link1).isNotNull();
assertThat(link1.getEntity1()).isSameAs(attribute1);
assertThat(link1.getEntity2()).isSameAs(entity);
Link link2 = diagram.getLinks().get(1);
assertThat(link2).isNotNull();
assertThat(link2.getEntity1()).isSameAs(attribute2);
assertThat(link2.getEntity2()).isSameAs(attribute1);
}
@Test
void test_executeNonNested() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E {"));
CommandExecutionResult result1 = command.execute(diagram, BlocLines.singleString("attr1"));
CommandExecutionResult result2 = command.execute(diagram, BlocLines.singleString("attr2"));
assertThat(result1).matches(CommandExecutionResult::isOk);
assertThat(result2).matches(CommandExecutionResult::isOk);
Entity entity = diagram.quarkInContext(true, "E").getData();
assertThat(entity).isNotNull();
Entity attribute1 = diagram.quarkInContext(true, "E/attr1").getData();
assertThat(attribute1).isNotNull();
assertThat(attribute1.getDisplay().toString()).isEqualTo("[attr1]");
Entity attribute2 = diagram.quarkInContext(true, "E/attr2").getData();
assertThat(attribute2).isNotNull();
assertThat(attribute2.getDisplay().toString()).isEqualTo("[attr2]");
assertThat(diagram.getLinks().size()).isEqualTo(2);
Link link1 = diagram.getLinks().get(0);
assertThat(link1).isNotNull();
assertThat(link1.getEntity1()).isSameAs(attribute1);
assertThat(link1.getEntity2()).isSameAs(entity);
Link link2 = diagram.getLinks().get(1);
assertThat(link2).isNotNull();
assertThat(link2.getEntity1()).isSameAs(attribute2);
assertThat(link2.getEntity2()).isSameAs(entity);
}
}

View File

@ -0,0 +1,123 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.abel.Entity;
import net.sourceforge.plantuml.abel.LeafType;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
public class CommandCreateEntityTest {
private final Command<ChenEerDiagram> command = new CommandCreateEntity();
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parseEntity() {
IRegex regex = CommandCreateEntity.getRegexConcat();
RegexResult matcher = regex.matcher("entity Bus_123 {");
assertThat(matcher).isNotNull();
assertThat(matcher.get("TYPE", 0)).isEqualTo("entity");
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("Bus_123");
assertThat(matcher.get("STEREO", 0)).isNull();
}
@Test
void test_parseRelationship() {
IRegex regex = CommandCreateEntity.getRegexConcat();
RegexResult matcher = regex.matcher("relationship Drives {");
assertThat(matcher).isNotNull();
assertThat(matcher.get("TYPE", 0)).isEqualTo("relationship");
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("Drives");
assertThat(matcher.get("STEREO", 0)).isNull();
}
@Test
void test_parseInvalid() {
IRegex regex = CommandCreateEntity.getRegexConcat();
RegexResult matcher = regex.matcher("class MyClass {");
assertThat(matcher).isNull();
}
@Test
void test_parseEntityWithStereo() {
IRegex regex = CommandCreateEntity.getRegexConcat();
RegexResult matcher = regex.matcher("entity Bus_123 <<red>> {");
assertThat(matcher).isNotNull();
assertThat(matcher.get("TYPE", 0)).isEqualTo("entity");
assertThat(matcher.get("DISPLAY", 0)).isNull();
assertThat(matcher.get("CODE", 0)).isEqualTo("Bus_123");
assertThat(matcher.get("STEREO", 0)).isEqualTo("<<red>>");
}
@Test
void test_parseEntityWithDisplayName() {
IRegex regex = CommandCreateEntity.getRegexConcat();
RegexResult matcher = regex.matcher("entity \"Red Bus :)\" as Bus_123 <<red>> {");
assertThat(matcher).isNotNull();
assertThat(matcher.get("TYPE", 0)).isEqualTo("entity");
assertThat(matcher.get("DISPLAY", 0)).isEqualTo("Red Bus :)");
assertThat(matcher.get("CODE", 0)).isEqualTo("Bus_123");
assertThat(matcher.get("STEREO", 0)).isEqualTo("<<red>>");
}
@Test
void test_executeWithEntity() throws NoSuchColorException {
BlocLines lines = BlocLines.singleString("entity \"display\" as code <<stereo>> {");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Entity entity = diagram.quarkInContext(true, "code").getData();
assertThat(entity).isNotNull();
assertThat(entity.getLeafType()).isEqualTo(LeafType.CHEN_ENTITY);
assertThat(entity.getDisplay().toString()).isEqualTo("[display]");
assertThat(entity.getStereotype().toString()).isEqualTo("<<stereo>>");
assertThat(diagram.peekOwner()).isSameAs(entity);
}
@Test
void test_executeWithRelationship() throws NoSuchColorException {
BlocLines lines = BlocLines.singleString("relationship \"display\" as code <<stereo>> {");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Entity entity = diagram.quarkInContext(true, "code").getData();
assertThat(entity).isNotNull();
assertThat(entity.getLeafType()).isEqualTo(LeafType.CHEN_RELATIONSHIP);
assertThat(entity.getDisplay().toString()).isEqualTo("[display]");
assertThat(entity.getStereotype().toString()).isEqualTo("<<stereo>>");
assertThat(diagram.peekOwner()).isSameAs(entity);
}
@Test
void test_executeWithEntityWhenAlreadyExists() throws NoSuchColorException {
BlocLines lines = BlocLines.singleString("entity \"display\" as code <<stereo>> {");
command.execute(diagram, lines);
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
}
}

View File

@ -0,0 +1,49 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
@ExtendWith(MockitoExtension.class)
public class CommandEndGroupTest {
private final Command<ChenEerDiagram> command = new CommandEndGroup();
@Spy
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parse() {
IRegex regex = CommandEndGroup.getRegexConcat();
RegexResult matcher = regex.matcher("}");
assertThat(matcher).isNotNull();
}
@Test
void test_execute() throws NoSuchColorException {
diagram.pushOwner(null);
BlocLines lines = BlocLines.singleString("}");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Mockito.verify(diagram).popOwner();
}
}

View File

@ -0,0 +1,213 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.decoration.LinkMiddleDecor;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
public class CommandMultiSubclassTest {
private final Command<ChenEerDiagram> command = new CommandMultiSubclass();
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parse() {
IRegex regex = CommandMultiSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 ->- d { E2, E3, E4 }");
assertThat(matcher).isNotNull();
assertThat(matcher.get("SUPERCLASS", 0)).isEqualTo("E1");
assertThat(matcher.get("SUBCLASSES", 0)).isEqualTo(" E2, E3, E4 ");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("SYMBOL", 0)).isEqualTo("d");
}
@Test
void test_parseWithCompulsoryParticipation() {
IRegex regex = CommandMultiSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 =>= d { E2, E3, E4 }");
assertThat(matcher).isNotNull();
assertThat(matcher.get("SUPERCLASS", 0)).isEqualTo("E1");
assertThat(matcher.get("SUBCLASSES", 0)).isEqualTo(" E2, E3, E4 ");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("=");
assertThat(matcher.get("SYMBOL", 0)).isEqualTo("d");
}
@Test
void test_parseOverlapping() {
IRegex regex = CommandMultiSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 ->- o { E2, E3, E4 }");
assertThat(matcher).isNotNull();
assertThat(matcher.get("SUPERCLASS", 0)).isEqualTo("E1");
assertThat(matcher.get("SUBCLASSES", 0)).isEqualTo(" E2, E3, E4 ");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("SYMBOL", 0)).isEqualTo("o");
}
@Test
void test_parseCategory() {
IRegex regex = CommandMultiSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 ->- U { E2, E3, E4 }");
assertThat(matcher).isNotNull();
assertThat(matcher.get("SUPERCLASS", 0)).isEqualTo("E1");
assertThat(matcher.get("SUBCLASSES", 0)).isEqualTo(" E2, E3, E4 ");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("SYMBOL", 0)).isEqualTo("U");
}
@Test
void test_executeOverlappingSubclasses() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E3 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E4 {"));
BlocLines lines = BlocLines.singleString("E1 ->- o { E2, E3, E4 }");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(4);
Link link1 = diagram.getLinks().get(0);
assertThat(link1.getEntity1().getName()).isEqualTo("E1");
assertThat(link1.getEntity2().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link1.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link1.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link2 = diagram.getLinks().get(1);
assertThat(link2.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link2.getEntity2().getName()).isEqualTo("E2");
assertThat(link2.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link2.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link3 = diagram.getLinks().get(2);
assertThat(link3.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link3.getEntity2().getName()).isEqualTo("E3");
assertThat(link3.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link3.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link4 = diagram.getLinks().get(3);
assertThat(link4.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link4.getEntity2().getName()).isEqualTo("E4");
assertThat(link4.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link4.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
}
@Test
void test_executeOverlappingSubclassesWithCompulsaryParticipation() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E3 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E4 {"));
BlocLines lines = BlocLines.singleString("E1 =>= o { E2, E3, E4 }");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(4);
Link link1 = diagram.getLinks().get(0);
assertThat(link1.getEntity1().getName()).isEqualTo("E1");
assertThat(link1.getEntity2().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link1.getType().getStyle().getStroke3().getThickness()).isEqualTo(2);
assertThat(link1.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link2 = diagram.getLinks().get(1);
assertThat(link2.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link2.getEntity2().getName()).isEqualTo("E2");
assertThat(link2.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link2.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link3 = diagram.getLinks().get(2);
assertThat(link3.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link3.getEntity2().getName()).isEqualTo("E3");
assertThat(link3.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link3.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link4 = diagram.getLinks().get(3);
assertThat(link4.getEntity1().getName()).isEqualTo("E1/o E2, E3, E4 /center");
assertThat(link4.getEntity2().getName()).isEqualTo("E4");
assertThat(link4.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link4.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
}
@Test
void test_executeCategories() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E3 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E4 {"));
BlocLines lines = BlocLines.singleString("E1 ->- U { E2, E3, E4 }");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(4);
Link link1 = diagram.getLinks().get(0);
assertThat(link1.getEntity1().getName()).isEqualTo("E1");
assertThat(link1.getEntity2().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link1.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link1.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link2 = diagram.getLinks().get(1);
assertThat(link2.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link2.getEntity2().getName()).isEqualTo("E2");
assertThat(link2.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link2.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link3 = diagram.getLinks().get(2);
assertThat(link3.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link3.getEntity2().getName()).isEqualTo("E3");
assertThat(link3.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link3.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link4 = diagram.getLinks().get(3);
assertThat(link4.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link4.getEntity2().getName()).isEqualTo("E4");
assertThat(link4.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link4.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
}
@Test
void test_executeCategoriesWithCompulsaryParticipation() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E3 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E4 {"));
BlocLines lines = BlocLines.singleString("E1 =>= U { E2, E3, E4 }");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
assertThat(diagram.getLinks().size()).isEqualTo(4);
Link link1 = diagram.getLinks().get(0);
assertThat(link1.getEntity1().getName()).isEqualTo("E1");
assertThat(link1.getEntity2().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link1.getType().getStyle().getStroke3().getThickness()).isEqualTo(2);
assertThat(link1.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
Link link2 = diagram.getLinks().get(1);
assertThat(link2.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link2.getEntity2().getName()).isEqualTo("E2");
assertThat(link2.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link2.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link3 = diagram.getLinks().get(2);
assertThat(link3.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link3.getEntity2().getName()).isEqualTo("E3");
assertThat(link3.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link3.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
Link link4 = diagram.getLinks().get(3);
assertThat(link4.getEntity1().getName()).isEqualTo("E1/U E2, E3, E4 /center");
assertThat(link4.getEntity2().getName()).isEqualTo("E4");
assertThat(link4.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link4.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.NONE);
}
}

View File

@ -0,0 +1,112 @@
package net.sourceforge.plantuml.cheneer.command;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import net.sourceforge.plantuml.abel.Link;
import net.sourceforge.plantuml.cheneer.ChenEerDiagram;
import net.sourceforge.plantuml.command.Command;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.decoration.LinkMiddleDecor;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.utils.BlocLines;
public class CommandSimpleSubclassTest {
private final Command<ChenEerDiagram> command = new CommandSimpleSubclass();
private final ChenEerDiagram diagram = new ChenEerDiagram(UmlSource.create(new ArrayList<>(), false), null);
@Test
void test_parseSubset() {
IRegex regex = CommandSimpleSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 -<- E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("DIRECTION", 0)).isEqualTo("<");
}
@Test
void test_parseSubsetWithCompulsoryParticipation() {
IRegex regex = CommandSimpleSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 =<= E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("=");
assertThat(matcher.get("DIRECTION", 0)).isEqualTo("<");
}
@Test
void test_parseSuperset() {
IRegex regex = CommandSimpleSubclass.getRegexConcat();
RegexResult matcher = regex.matcher("E1 ->- E2");
assertThat(matcher).isNotNull();
assertThat(matcher.get("NAME1", 0)).isEqualTo("E1");
assertThat(matcher.get("NAME2", 0)).isEqualTo("E2");
assertThat(matcher.get("PARTICIPATION", 0)).isEqualTo("-");
assertThat(matcher.get("DIRECTION", 0)).isEqualTo(">");
}
@Test
void test_executeSubset() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 -<- E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUBSET);
}
@Test
void test_executeSubsetWithCompulsoryParticipation() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 =<= E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(2);
assertThat(link.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUBSET);
}
@Test
void test_executeSuperset() throws NoSuchColorException {
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E1 {"));
new CommandCreateEntity().execute(diagram, BlocLines.singleString("entity E2 {"));
BlocLines lines = BlocLines.singleString("E1 ->- E2");
CommandExecutionResult result = command.execute(diagram, lines);
assertThat(result).matches(CommandExecutionResult::isOk);
Link link = diagram.getLinks().get(0);
assertThat(link.getEntity1().getName()).isEqualTo("E1");
assertThat(link.getEntity2().getName()).isEqualTo("E2");
assertThat(link.getType().getStyle().getStroke3().getThickness()).isEqualTo(1);
assertThat(link.getType().getMiddleDecor()).isEqualTo(LinkMiddleDecor.SUPERSET);
}
}

View File

@ -0,0 +1,94 @@
package nonreg.simple;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import nonreg.BasicTest;
/*
Test diagram MUST be put between triple quotes
"""
@startchen movies
entity "Director" as DIRECTOR {
"No." as Number <<key>>
Name {
Fname
Lname
}
Born : DATE
Died
Age
}
entity "Customer" as CUSTOMER {
Number <<key>>
Bonus <<derived>>
Name <<multi>>
}
entity "Movie" as MOVIE {
Code
}
relationship "was-rented-to" as RENTED_TO {
Date
}
RENTED_TO -1- CUSTOMER
RENTED_TO -N- MOVIE
RENTED_TO -(N,M)- DIRECTOR
entity "Parent" as PARENT {
}
entity "Member" as MEMBER {
}
CUSTOMER ->- PARENT
MEMBER -<- CUSTOMER
entity "Kid" as CHILD <<weak>> {
Name <<key>>
}
relationship "is-parent-of" as PARENT_OF <<identifying>> {
}
PARENT_OF -1- PARENT
PARENT_OF =N= CHILD
entity "Little Kid" as TODDLER {
FavoriteToy
}
entity "Primary-Aged Kid" as PRIMARY_AGE {
FavoriteColor
}
entity "Teenager" as TEEN {
Hobby
}
CHILD =>= d { TODDLER, PRIMARY_AGE, TEEN }
entity "Human" as PERSON {
}
PERSON ->- U { CUSTOMER, DIRECTOR }
@endchen
"""
*/
public class ChenMovieAlias_Test extends BasicTest {
@Test
void testSimple() throws IOException {
checkImage("(30 entities)");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
package nonreg.simple;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import nonreg.BasicTest;
/*
Test diagram MUST be put between triple quotes
"""
@startchen movies
entity DIRECTOR {
Number <<key>>
Name {
Fname
Lname
}
Born : DATE
Died
Age
}
entity CUSTOMER {
Number <<key>>
Bonus <<derived>>
Name <<multi>>
}
entity MOVIE {
Code
}
relationship RENTED_TO {
Date
}
RENTED_TO -1- CUSTOMER
RENTED_TO -N- MOVIE
RENTED_TO -(N,M)- DIRECTOR
entity PARENT {
}
entity MEMBER {
}
CUSTOMER ->- PARENT
MEMBER -<- CUSTOMER
entity CHILD <<weak>> {
Name <<key>>
}
relationship PARENT_OF <<identifying>> {
}
PARENT_OF -1- PARENT
PARENT_OF =N= CHILD
entity TODDLER {
FavoriteToy
}
entity PRIMARY_AGE {
FavoriteColor
}
entity TEEN {
Hobby
}
CHILD =>= d { TODDLER, PRIMARY_AGE, TEEN }
entity PERSON {
}
PERSON ->- U { CUSTOMER, DIRECTOR }
@endchen
"""
*/
public class ChenMovieExtended_Test extends BasicTest {
@Test
void testSimple() throws IOException {
checkImage("(30 entities)");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
package nonreg.simple;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import nonreg.BasicTest;
/*
Test diagram MUST be put between triple quotes
"""
@startchen movies
entity DIRECTOR {
Number <<key>>
Name {
Fname
Lname
}
Born : DATE
Died
Age
}
entity CUSTOMER {
Number <<key>>
Bonus <<derived>>
Name <<multi>>
}
entity MOVIE {
Code
}
relationship RENTED_TO {
Date
}
RENTED_TO -1- CUSTOMER
RENTED_TO -N- MOVIE
RENTED_TO -(N,M)- DIRECTOR
@endchen
"""
*/
public class ChenMovie_Test extends BasicTest {
@Test
void testSimple() throws IOException {
checkImage("(16 entities)");
}
}

View File

@ -0,0 +1,555 @@
package nonreg.simple;
public class ChenMovie_TestResult {
}
/*
"""
DPI: 96
dimension: [ 1357.2949 ; 320.0000 ]
scaleFactor: 1.0000
seed: 1893551533522493087
svgLinkTarget: _top
hoverPathColorRGB: null
preserveAspectRatio: none
RECTANGLE:
pt1: [ 411.5000 ; 281.0000 ]
pt2: [ 526.4536 ; 315.0000 ]
xCorner: 0
yCorner: 0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: DIRECTOR
position: [ 421.5000 ; 301.8889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 654.5000 ; 209.0000 ]
pt2: [ 773.7119 ; 237.6424 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Number
position: [ 669.6174 ; 225.8889 ]
orientation: 0
font: SansSerif.plain/14 [UNDERLINE]
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 74.0000 ; 210.5000 ]
pt2: [ 174.4750 ; 236.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Name
position: [ 90.8355 ; 227.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 138.5000 ; 107.5000 ]
pt2: [ 231.5753 ; 133.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Fname
position: [ 154.2519 ; 124.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 6.0000 ; 106.5000 ]
pt2: [ 120.0711 ; 134.1142 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Lname
position: [ 21.8671 ; 123.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 192.0000 ; 203.0000 ]
pt2: [ 371.7951 ; 243.7590 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Born : DATE
position: [ 202.3602 ; 219.8889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 390.0000 ; 210.5000 ]
pt2: [ 463.9416 ; 236.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Died
position: [ 402.9498 ; 227.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 482.0000 ; 210.5000 ]
pt2: [ 540.3515 ; 236.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Age
position: [ 492.6667 ; 227.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
RECTANGLE:
pt1: [ 917.0000 ; 281.0000 ]
pt2: [ 1049.4781 ; 315.0000 ]
xCorner: 0
yCorner: 0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: CUSTOMER
position: [ 927.0000 ; 301.8889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 1091.5000 ; 209.0000 ]
pt2: [ 1210.7119 ; 237.6424 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Number
position: [ 1106.6174 ; 225.8889 ]
orientation: 0
font: SansSerif.plain/14 [UNDERLINE]
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 791.5000 ; 209.5000 ]
pt2: [ 906.1275 ; 237.2255 ]
start: 0.0
extend: 0.0
stroke: 10.0-10.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Bonus
position: [ 807.2808 ; 226.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 973.0000 ; 210.5000 ]
pt2: [ 1073.4750 ; 236.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
ELLIPSE:
pt1: [ 976.0000 ; 213.5000 ]
pt2: [ 1070.4750 ; 233.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Name
position: [ 989.8355 ; 227.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
RECTANGLE:
pt1: [ 1274.0000 ; 281.0000 ]
pt2: [ 1352.2949 ; 315.0000 ]
xCorner: 0
yCorner: 0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: MOVIE
position: [ 1284.0000 ; 301.8889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 1228.5000 ; 210.5000 ]
pt2: [ 1317.6043 ; 236.2990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Code
position: [ 1243.6703 ; 227.3889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
POLYGON:
points:
- [ 852.5000 ; 120.3553 ]
- [ 943.2105 ; 75.0000 ]
- [ 1033.9210 ; 120.3553 ]
- [ 943.2105 ; 165.7105 ]
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: RENTED_TO
position: [ 877.6803 ; 124.2441 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
ELLIPSE:
pt1: [ 901.0000 ; 12.0000 ]
pt2: [ 984.5211 ; 37.7990 ]
start: 0.0
extend: 0.0
stroke: 0.0-0.0-0.5
shadow: 0
color: ff181818
backcolor: fff1f1f1
TEXT:
text: Date
position: [ 915.3527 ; 28.8889 ]
orientation: 0
font: SansSerif.plain/14 []
color: ff000000
extendedColor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 665.3328 ; 232.0606 ]
- type: SEG_CUBICTO
pt1: [ 641.4594 ; 239.5693 ]
pt2: [ 611.1286 ; 248.9786 ]
pt3: [ 584.0000 ; 257.0000 ]
- type: SEG_CUBICTO
pt1: [ 563.3889 ; 263.0943 ]
pt2: [ 540.7253 ; 269.5323 ]
pt3: [ 520.6400 ; 275.1474 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 151.7733 ; 230.5368 ]
- type: SEG_CUBICTO
pt1: [ 159.9663 ; 233.2134 ]
pt2: [ 168.7450 ; 235.8814 ]
pt3: [ 177.0000 ; 238.0000 ]
- type: SEG_CUBICTO
pt1: [ 255.1385 ; 258.0537 ]
pt2: [ 347.1264 ; 273.7179 ]
pt3: [ 405.2514 ; 282.6619 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 171.5202 ; 127.8846 ]
- type: SEG_CUBICTO
pt1: [ 159.7490 ; 147.3746 ]
pt2: [ 137.0409 ; 184.9732 ]
pt3: [ 125.3504 ; 204.3296 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 64.9747 ; 128.7040 ]
- type: SEG_CUBICTO
pt1: [ 76.8591 ; 148.3814 ]
pt2: [ 99.0526 ; 185.1281 ]
pt3: [ 110.5964 ; 204.2416 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 326.6049 ; 238.1195 ]
- type: SEG_CUBICTO
pt1: [ 356.3505 ; 249.6519 ]
pt2: [ 393.4909 ; 264.0513 ]
pt3: [ 421.5014 ; 274.9110 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 428.1247 ; 230.7986 ]
- type: SEG_CUBICTO
pt1: [ 435.1459 ; 242.9185 ]
pt2: [ 445.9126 ; 261.5039 ]
pt3: [ 453.6250 ; 274.8170 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 497.8753 ; 230.7986 ]
- type: SEG_CUBICTO
pt1: [ 490.8541 ; 242.9185 ]
pt2: [ 480.0874 ; 261.5039 ]
pt3: [ 472.3750 ; 274.8170 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 1113.4005 ; 232.1367 ]
- type: SEG_CUBICTO
pt1: [ 1085.2657 ; 244.2782 ]
pt2: [ 1044.0729 ; 262.0549 ]
pt3: [ 1014.3238 ; 274.8930 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 867.2664 ; 231.6292 ]
- type: SEG_CUBICTO
pt1: [ 889.7416 ; 243.7893 ]
pt2: [ 923.2277 ; 261.9068 ]
pt3: [ 947.2935 ; 274.9275 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 1010.2145 ; 230.7986 ]
- type: SEG_CUBICTO
pt1: [ 1003.5277 ; 242.9185 ]
pt2: [ 993.2737 ; 261.5039 ]
pt3: [ 985.9285 ; 274.8170 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 1273.7855 ; 230.7986 ]
- type: SEG_CUBICTO
pt1: [ 1280.4723 ; 242.9185 ]
pt2: [ 1290.7263 ; 261.5039 ]
pt3: [ 1298.0715 ; 274.8170 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 937.0000 ; 32.2235 ]
- type: SEG_CUBICTO
pt1: [ 937.0000 ; 41.6286 ]
pt2: [ 937.0000 ; 55.2460 ]
pt3: [ 937.0000 ; 68.6542 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 935.4608 ; 160.0549 ]
- type: SEG_CUBICTO
pt1: [ 935.8346 ; 183.6946 ]
pt2: [ 938.2163 ; 212.9522 ]
pt3: [ 946.0000 ; 238.0000 ]
- type: SEG_CUBICTO
pt1: [ 950.1123 ; 251.2335 ]
pt2: [ 958.0422 ; 264.6940 ]
pt3: [ 964.8519 ; 274.7384 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
EMPTY:
pt1: [ 952.0000 ; 217.0000 ]
pt2: [ 964.3969 ; 230.0000 ]
TEXT:
text: 1
position: [ 953.0000 ; 226.5556 ]
orientation: 0
font: SansSerif.plain/11 []
color: ff000000
extendedColor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 1027.7972 ; 121.8769 ]
- type: SEG_CUBICTO
pt1: [ 1129.4735 ; 131.1293 ]
pt2: [ 1284.8054 ; 152.3173 ]
pt3: [ 1321.0000 ; 197.0000 ]
- type: SEG_CUBICTO
pt1: [ 1338.9667 ; 219.1802 ]
pt2: [ 1326.2568 ; 254.3104 ]
pt3: [ 1316.1265 ; 274.7586 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
EMPTY:
pt1: [ 1336.0000 ; 217.0000 ]
pt2: [ 1348.5088 ; 230.0000 ]
TEXT:
text: N
position: [ 1337.0000 ; 226.5556 ]
orientation: 0
font: SansSerif.plain/11 []
color: ff000000
extendedColor: NULL_COLOR
PATH:
- type: SEG_MOVETO
pt1: [ 846.3294 ; 120.6490 ]
- type: SEG_CUBICTO
pt1: [ 770.3065 ; 128.4455 ]
pt2: [ 661.2914 ; 148.0025 ]
pt3: [ 579.0000 ; 197.0000 ]
- type: SEG_CUBICTO
pt1: [ 558.1640 ; 209.4060 ]
pt2: [ 561.2682 ; 222.0525 ]
pt3: [ 543.0000 ; 238.0000 ]
- type: SEG_CUBICTO
pt1: [ 527.0605 ; 251.9147 ]
pt2: [ 507.2472 ; 265.0672 ]
pt3: [ 491.3266 ; 274.7892 ]
stroke: 0.0-0.0-1.0
shadow: 0
color: ff181818
backcolor: NULL_COLOR
EMPTY:
pt1: [ 585.0000 ; 217.0000 ]
pt2: [ 645.5178 ; 230.0000 ]
TEXT:
text: (N,M)
position: [ 586.0000 ; 226.5556 ]
orientation: 0
font: SansSerif.plain/11 []
color: ff000000
extendedColor: NULL_COLOR
"""
*/