diff --git a/code/src/java/plugin/exporttokens/PortraitToken.java b/code/src/java/plugin/exporttokens/PortraitToken.java index f4fa65d9d3b..a9a413df8ff 100644 --- a/code/src/java/plugin/exporttokens/PortraitToken.java +++ b/code/src/java/plugin/exporttokens/PortraitToken.java @@ -40,7 +40,7 @@ * The Class {@code PortraitToken} supports the PORTRAIT * token and its and PORTRAIT.THUMB variant. * - * + * */ public class PortraitToken extends AbstractExportToken { @@ -50,6 +50,16 @@ public String getTokenName() return "PORTRAIT"; } + /** + * True if the token should be encoded during the export + * @return False because the Portrait path must be unchanged + */ + @Override + public boolean isEncoded() + { + return false; + } + //TODO: Move this to a token that has all of the descriptive stuff about a character @Override public String getToken(String tokenSource, CharacterDisplay display, ExportHandler eh) @@ -113,9 +123,9 @@ private String getThumbnailToken(CharacterDisplay display) } /** - * Generate a thumbnail image based on the character's portrait and + * Generate a thumbnail image based on the character's portrait and * the thumnbnail rectangle. - * + * * @param display The character being output. * @return The thumbnail image, or null if not defined. */ diff --git a/code/src/test/pcgen/io/ExportHandlerTest.java b/code/src/test/pcgen/io/ExportHandlerTest.java index 26e3eb231b6..ab08cbb30fe 100644 --- a/code/src/test/pcgen/io/ExportHandlerTest.java +++ b/code/src/test/pcgen/io/ExportHandlerTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static pcgen.util.TestHelper.evaluateToken; import java.io.BufferedWriter; import java.io.File; @@ -91,7 +92,7 @@ protected void setUp() throws Exception LoadContext context = Globals.getContext(); BonusObj aBonus = Bonus.newBonus(context, "MODSKILLPOINTS|NUMBER|INT"); - + if (aBonus != null) { intel.addToListFor(ListKey.BONUS, aBonus); @@ -137,13 +138,13 @@ protected void setUp() throws Exception balance.addToListFor(ListKey.TYPE, Type.getConstant("DEX")); balance.put(ObjectKey.KEY_STAT, dexRef); aBonus = Bonus.newBonus(context, "SKILL|Balance|2|PRESKILL:1,Tumble=5|TYPE=Synergy.STACK"); - + if (aBonus != null) { balance.addToListFor(ListKey.BONUS, aBonus); } Globals.getContext().getReferenceContext().importObject(balance); - + weapon = new Equipment(); weapon.setName("TestWpn"); weapon.addToListFor(ListKey.TYPE, Type.WEAPON); @@ -152,7 +153,7 @@ protected void setUp() throws Exception gem.setName("TestGem"); gem.addToListFor(ListKey.TYPE, Type.getConstant("gem")); gem.setQty(1); - + armor = new Equipment(); armor.setName("TestArmorSuit"); TestHelper.addType(armor, "armor.suit"); @@ -262,31 +263,31 @@ public void testFor() throws IOException Ability dummyFeat1 = new Ability(); dummyFeat1.setName("1"); dummyFeat1.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat2 = new Ability(); dummyFeat2.setName("2"); dummyFeat2.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat3 = new Ability(); dummyFeat3.setName("3"); dummyFeat3.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat4 = new Ability(); dummyFeat4.setName("4"); dummyFeat4.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat5 = new Ability(); dummyFeat5.setName("5"); dummyFeat5.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat6 = new Ability(); dummyFeat6.setName("6"); dummyFeat6.setCDOMCategory(BuildUtilities.getFeatCat()); - + Ability dummyFeat7 = new Ability(); dummyFeat7.setName("7"); - dummyFeat7.setCDOMCategory(BuildUtilities.getFeatCat()); - + dummyFeat7.setCDOMCategory(BuildUtilities.getFeatCat()); + addAbility(BuildUtilities.getFeatCat(), dummyFeat1); addAbility(BuildUtilities.getFeatCat(), dummyFeat2); addAbility(BuildUtilities.getFeatCat(), dummyFeat3); @@ -294,7 +295,7 @@ public void testFor() throws IOException addAbility(BuildUtilities.getFeatCat(), dummyFeat5); addAbility(BuildUtilities.getFeatCat(), dummyFeat6); addAbility(BuildUtilities.getFeatCat(), dummyFeat7); - + assertEquals("----------------", evaluateToken( "FOR.1,((24-STRLEN[SKILL.0])),24,-,NONE,NONE,1", pc), "Test for evaluates correctly" @@ -303,7 +304,7 @@ public void testFor() throws IOException evaluateToken( "FOR.1,((24-STRLEN[SKILL.0])),24, ,NONE,NONE,1", pc), "Test for evaluates correctly" ); - + String tok = "DFOR." + "0" + ",${((count(\"ABILITIES\";\"CATEGORY=FEAT\")+1)/2)}" @@ -314,20 +315,20 @@ public void testFor() throws IOException + ",[" + ",]" + ",0"; - - + + //Logging.errorPrint( "DFOR Test: " + evaluateToken(tok, pc)); - - - // Test DFOR with alternate syntax for jep passthrough. ie, anything + + + // Test DFOR with alternate syntax for jep passthrough. ie, anything // surrounded by ${x} will tbe sent straight to be processed. We - // will assume that x is a well formed type of value. This was to get around + // will assume that x is a well formed type of value. This was to get around // the problems with DFOR not taking ((count("ABILITIES";"CATEGORY=FEAT")+1) // since it could not figure out how to parse it to send to the right place. assertEquals("[ 1 5 ][ 2 6 ][ 3 7 ][ 4 ]", evaluateToken(tok, pc), "Test for DFOR " ); - + } @Test @@ -347,7 +348,7 @@ public void testForNoMoreItems() throws IOException evaluateToken( "FOR.0,100,1,\\ARMOR.SUIT.ALL.%.NAME\\,S,F,1", pc), "Test for evaluates correctly" ); - + } @Test @@ -368,12 +369,12 @@ public void testExpressionOutput() throws IOException dummyFeat2.setName("DummyFeat2"); dummyFeat2.setCDOMCategory(BuildUtilities.getFeatCat()); final BonusObj aBonus = Bonus.newBonus(context, "VAR|NegLevels|7"); - + if (aBonus != null) { dummyFeat2.addToListFor(ListKey.BONUS, aBonus); } - + AbilityCategory cat = context.getReferenceContext().constructCDOMObject( AbilityCategory.class, "Maneuver"); AbilityCategory cat2 = context.getReferenceContext().constructCDOMObject( @@ -381,49 +382,49 @@ public void testExpressionOutput() throws IOException Ability dummyFeat3 = new Ability(); dummyFeat3.setName("DummyFeat3"); dummyFeat3.setCDOMCategory(cat); - + Ability dummyFeat4 = new Ability(); dummyFeat4.setName("DummyFeat4"); dummyFeat4.setCDOMCategory(cat2); - + addAbility(BuildUtilities.getFeatCat(), dummyFeat); addAbility(BuildUtilities.getFeatCat(), dummyFeat2); addAbility(cat, dummyFeat3); addAbility(cat2, dummyFeat4); - + assertEquals("7", evaluateToken( "VAR.NegLevels.INTVAL", pc), "Unsigned output"); assertEquals("+7", evaluateToken( "VAR.NegLevels.INTVAL.SIGN", pc), "Signed output"); - + String tok; - - tok = "count(\"ABILITIES\", \"CATEGORY=Maneuver\")"; + + tok = "count(\"ABILITIES\", \"CATEGORY=Maneuver\")"; // if this evaluates math wise, the values should be string "1.0" assertNotEquals("1.0", evaluateToken(tok, pc), "Token: |" + tok + "| != 1.0: "); - + tok = "VAR.count(\"ABILITIES\", \"CATEGORY=Maneuver\")"; assertEquals("1.0", evaluateToken(tok, pc), "Token: |" + tok + "| == 1.0: "); - + tok = "COUNT[\"ABILITIES\", \"CATEGORY=Maneuver\"]"; assertNotEquals("1.0", evaluateToken(tok, pc), "Token: |" + tok + "| != 1.0: "); - + tok = "count(\"ABILITIES\", \"CATEGORY=Maneuver(Special)\")"; assertNotEquals("1.0", evaluateToken(tok, pc), "Token: |" + tok + "| != 1.0 "); - + tok = "${count(\"ABILITIES\", \"CATEGORY=Maneuver(Special)\")+5}"; assertNotEquals("5.0", evaluateToken(tok, pc), "Token: |" + tok + "| == 5.0 "); - + tok = "${count(\"ABILITIES\", \"CATEGORY=Maneuver(Special)\")+5}"; assertEquals("6.0", evaluateToken(tok, pc), "Token: |" + tok + "| != 6.0 "); - + tok = "${(count(\"ABILITIES\", \"CATEGORY=Maneuver(Special)\")+5)/3}"; assertNotEquals("3.0", evaluateToken(tok, pc), "Token: |" + tok + "| == 3.0 "); - + tok = "${(count(\"ABILITIES\", \"CATEGORY=Maneuver(Special)\")+5)/3}"; assertEquals("2.0", evaluateToken(tok, pc), "Token: |" + tok + "| != 2.0 "); - - + + } @Test @@ -446,20 +447,6 @@ public void testPartyFor() throws IOException ); } - private static String evaluateToken(String token, PlayerCharacter pc) - throws IOException - { - StringWriter retWriter = new StringWriter(); - BufferedWriter bufWriter = new BufferedWriter(retWriter); - ExportHandler export = ExportHandler.createExportHandler(new File("")); - export.replaceToken(token, bufWriter, pc); - retWriter.flush(); - - bufWriter.flush(); - - return retWriter.toString(); - } - private static String evaluatePartyToken(String token, List pcs) throws IOException { @@ -490,6 +477,6 @@ protected void defaultSetupEnd() { //We will handle this locally } - - + + } diff --git a/code/src/test/pcgen/util/TestHelper.java b/code/src/test/pcgen/util/TestHelper.java index 728007a08a8..a4810eae80a 100644 --- a/code/src/test/pcgen/util/TestHelper.java +++ b/code/src/test/pcgen/util/TestHelper.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.io.StringWriter; import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; @@ -64,6 +65,7 @@ import pcgen.core.bonus.Bonus; import pcgen.core.bonus.BonusObj; import pcgen.core.spell.Spell; +import pcgen.io.ExportHandler; import pcgen.persistence.CampaignFileLoader; import pcgen.persistence.GameModeFileLoader; import pcgen.persistence.PersistenceLayerException; @@ -123,7 +125,7 @@ public static void makeSizeAdjustments() .silentlyGetConstructedCDOMObject(SizeAdjustment.class, "M").put( ObjectKey.IS_DEFAULT_SIZE, true); } - + /** * Make some equipment * @param input Equipment source line to be parsed @@ -316,8 +318,8 @@ public static boolean makeAbilityFromString(final String input) return false; } - - + + /** * Set the important info about a WeaponProf * @param name The weaponprof name @@ -399,7 +401,7 @@ public static Spell makeSpell(final String name) } /** - * Set the important info about a Kit. Note the key of the kit created will + * Set the important info about a Kit. Note the key of the kit created will * be the provided name with KEY_ added at the front. e.g. KEY_name * @param name The kit name * @return The kit (which has also been added to global storage) @@ -428,7 +430,7 @@ public static PCTemplate makeTemplate(final String name) Globals.getContext().getReferenceContext().importObject(aTemplate); return aTemplate; } - + /** * Get the Ability Category of the Ability object passed in. If it does * not exist in the game mode, a new object wil be created and added to @@ -453,7 +455,7 @@ public static void addType(CDOMObject cdo, String string) /** * Checks to see if this PC has the weapon proficiency key aKey - * + * * @param aKey * @return boolean */ @@ -466,9 +468,9 @@ public static boolean hasWeaponProfKeyed(PlayerCharacter pc, } /** - * Locate the data folder which contains the primary set of LST data. This - * defaults to the data folder under the current directory, but can be - * customised in the config.ini folder. + * Locate the data folder which contains the primary set of LST data. This + * defaults to the data folder under the current directory, but can be + * customised in the config.ini folder. * @return The path of the data folder. */ public static String findDataFolder() @@ -522,7 +524,7 @@ public static void createDummySettingsFile(String configFileName, } } - + public static void loadGameModes(String testConfigFile) { String configFolder = "testsuite"; @@ -585,4 +587,25 @@ public static PCClass parsePCClassText(String classPCCText, } return reconstClass; } + + /** + * Evaluate a token, used in several "export" tests. By default, the token encoding is ignored. + * If encoded value is required, use @see pcgen.io.FileAccess#setCurrentOutputFilter(java.lang.String) before + * calling this static method. + * + * @param token the token to evaluate (e.g., any token from @see plugin.exporttokens such as "PORTRAIT") + * @param pc the pc or a PlayerCharacter object + * @return the string containing the evaluated token + * @throws IOException Signals that an I/O exception has occurred. + */ + public static String evaluateToken(String token, PlayerCharacter pc) + throws IOException { + StringWriter retWriter = new StringWriter(); + try (BufferedWriter bufWriter = new BufferedWriter(retWriter)) { + ExportHandler export = ExportHandler.createExportHandler(new File("")); + export.replaceTokenSkipMath(pc, token, bufWriter); + } + + return retWriter.toString(); + } } diff --git a/code/src/test/plugin/exporttokens/BioTokenTest.java b/code/src/test/plugin/exporttokens/BioTokenTest.java index 8525f8fa1b6..a6905c09730 100644 --- a/code/src/test/plugin/exporttokens/BioTokenTest.java +++ b/code/src/test/plugin/exporttokens/BioTokenTest.java @@ -18,16 +18,11 @@ package plugin.exporttokens; import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; +import static pcgen.util.TestHelper.evaluateToken; import pcgen.AbstractCharacterTestCase; import pcgen.cdom.enumeration.PCStringKey; import pcgen.core.PlayerCharacter; -import pcgen.io.ExportHandler; import pcgen.io.FileAccess; import org.junit.jupiter.api.BeforeEach; @@ -92,27 +87,4 @@ public void testBioExport() throws Exception "

Test bio <br/>entry,

\n

2nd line,

\n

Third line,

\n

last one,

"; assertEquals(expected, actual, "New Style Bio start only"); } - - /** - * Evaluate token. - * - * @param token the token - * @param pc the pc - * @return the string - * @throws IOException Signals that an I/O exception has occurred. - */ - private static String evaluateToken(String token, PlayerCharacter pc) - throws IOException - { - StringWriter retWriter = new StringWriter(); - BufferedWriter bufWriter = new BufferedWriter(retWriter); - ExportHandler export = ExportHandler.createExportHandler(new File("")); - export.replaceTokenSkipMath(pc, token, bufWriter); - retWriter.flush(); - - bufWriter.flush(); - - return retWriter.toString(); - } - } diff --git a/code/src/test/plugin/exporttokens/CampaignHistoryTokenTest.java b/code/src/test/plugin/exporttokens/CampaignHistoryTokenTest.java index 04e569bb6f3..064d96232dc 100644 --- a/code/src/test/plugin/exporttokens/CampaignHistoryTokenTest.java +++ b/code/src/test/plugin/exporttokens/CampaignHistoryTokenTest.java @@ -18,16 +18,13 @@ package plugin.exporttokens; import static org.junit.Assert.assertEquals; +import static pcgen.util.TestHelper.evaluateToken; -import java.io.BufferedWriter; -import java.io.File; import java.io.IOException; -import java.io.StringWriter; import pcgen.AbstractCharacterTestCase; import pcgen.core.ChronicleEntry; import pcgen.core.PlayerCharacter; -import pcgen.io.ExportHandler; import pcgen.io.FileAccess; import pcgen.util.TestHelper; @@ -35,10 +32,10 @@ import org.junit.jupiter.api.Test; /** - * CampaignHistoryTokenTest validates the functions of the + * CampaignHistoryTokenTest validates the functions of the * CampaignHistoryToken class. - * - * + * + * */ public class CampaignHistoryTokenTest extends AbstractCharacterTestCase { @@ -62,7 +59,7 @@ public void setUp() throws Exception hiddenEntry = TestHelper.buildChronicleEntry(false, "Campaign", "Date", "GM", "Party", "Adventure", 1390, "Chronicle"); - + character.addChronicleEntry(visibleEntry); character.addChronicleEntry(hiddenEntry); } @@ -122,20 +119,4 @@ public void testVisibility() throws IOException assertEquals("Invalid visibility", "", evaluateToken("CAMPAIGNHISTORY.LALALA.0.ADVENTURE", character)); } - - - private String evaluateToken(String token, PlayerCharacter pc) - throws IOException - { - StringWriter retWriter = new StringWriter(); - BufferedWriter bufWriter = new BufferedWriter(retWriter); - ExportHandler export = ExportHandler.createExportHandler(new File("")); - export.replaceTokenSkipMath(pc, token, bufWriter); - retWriter.flush(); - - bufWriter.flush(); - - return retWriter.toString(); - } - } diff --git a/code/src/test/plugin/exporttokens/PortraitTokenTest.java b/code/src/test/plugin/exporttokens/PortraitTokenTest.java index 3f3126c0fce..f5de88a7145 100644 --- a/code/src/test/plugin/exporttokens/PortraitTokenTest.java +++ b/code/src/test/plugin/exporttokens/PortraitTokenTest.java @@ -17,15 +17,17 @@ */ package plugin.exporttokens; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static pcgen.util.TestHelper.evaluateToken; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; +import java.nio.file.FileSystems; import javax.imageio.ImageIO; @@ -34,6 +36,7 @@ import pcgen.core.PlayerCharacter; import org.junit.jupiter.api.Test; +import pcgen.io.FileAccess; /** * The Class {@code PortraitTokenTest} checks the function of PortraitToken. @@ -42,7 +45,7 @@ public class PortraitTokenTest extends AbstractCharacterTestCase { - private PortraitToken portraitToken = new PortraitToken(); + private final PortraitToken portraitToken = new PortraitToken(); /** * Check the generation of a thumbnail file for valid, no scaling conditions. @@ -56,12 +59,12 @@ public void testThumb() throws Exception pc.setPortraitPath("code/src/resources/pcgen/images/SplashPcgen_Alpha.png"); pc.setPortraitThumbnailRect(new Rectangle(160, 70, Constants.THUMBNAIL_SIZE, Constants.THUMBNAIL_SIZE)); String thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNotNull("THUMB should not be null ", thumbResult); - assertNotSame("Thumb should not be portrait", pc.getDisplay().getPortraitPath(), thumbResult); + assertNotNull(thumbResult, "THUMB should not be null "); + assertNotSame(pc.getDisplay().getPortraitPath(), thumbResult, "Thumb should not be portrait"); File thumbFile = new File(thumbResult); - assertTrue("File should exist", thumbFile.exists()); + assertTrue(thumbFile.exists(), "File should exist"); BufferedImage image = ImageIO.read(thumbFile); - assertNotNull("THUMB image should not be null ", image); + assertNotNull(image, "THUMB image should not be null"); } /** @@ -76,40 +79,59 @@ public void testThumbScaling() throws Exception pc.setPortraitPath("code/src/resources/pcgen/images/SplashPcgen_Alpha.png"); pc.setPortraitThumbnailRect(new Rectangle(160, 70, 140, 140)); String thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNotNull("THUMB should not be null ", thumbResult); - assertNotSame("Thumb should not be portrait", pc.getDisplay().getPortraitPath(), thumbResult); + assertNotNull(thumbResult, "THUMB should not be null"); + assertNotSame(pc.getDisplay().getPortraitPath(), thumbResult, "Thumb should not be portrait"); File thumbFile = new File(thumbResult); - assertTrue("File should exist", thumbFile.exists()); + assertTrue(thumbFile.exists(), "File should exist"); BufferedImage image = ImageIO.read(thumbFile); - assertNotNull("THUMB image should not be null ", image); - assertEquals("Incorrect scaled width", Constants.THUMBNAIL_SIZE, image.getWidth()); - assertEquals("Incorrect scaled height", Constants.THUMBNAIL_SIZE, image.getHeight()); + assertNotNull(image, "THUMB image should not be null"); + assertEquals(Constants.THUMBNAIL_SIZE, image.getWidth(), "Incorrect scaled width"); + assertEquals(Constants.THUMBNAIL_SIZE, image.getHeight(), "Incorrect scaled height"); } /** * Check the generation of a thumbnail file for invalid conditions. - * @throws Exception Not expected. */ @Test - public void testThumbInvalid() throws Exception + public void testThumbInvalid() { PlayerCharacter pc = getCharacter(); pc.setName("PortraitTokenTest"); String thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNull("No image or rect should be null", thumbResult); - + assertNull(thumbResult, "No image or rect should be null"); + pc.setPortraitPath("code/src/resources/pcgen/images/SplashPcgen_Alpha.png"); thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNull("No rect should be null", thumbResult); + assertNull(thumbResult, "No rect should be null"); pc.setPortraitPath(""); pc.setPortraitThumbnailRect(new Rectangle(160, 70, 140, 140)); thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNull("No image should be null", thumbResult); + assertNull(thumbResult, "No image should be null"); pc.setPortraitPath("foo1gghas"); thumbResult = portraitToken.getToken("PORTRAIT.THUMB", pc, null); - assertNull("Invalid image should be null", thumbResult); + assertNull(thumbResult, "Invalid image should be null"); } + /** + * The portrait URI shouldn't be encoded, because if the path to the file has "unsafe" characters (e.g., '&') + * The generated XML uses FreeMarker's url_path, and it encodes URIs correctly. + * See OS-538 for further details. + * @throws Exception Not expected. + */ + @Test + public void testNonEncodedURI() throws Exception { + var inputPortraitPath = FileSystems.getDefault() + .getPath("code", "src", "resources", "pcgen", "D&D 3.Xe", "portrait.png") + .toString(); + + PlayerCharacter pc = getCharacter(); + pc.setName("PortraitTokenTest"); + pc.setPortraitPath(inputPortraitPath); + + FileAccess.setCurrentOutputFilter("xml"); + var outputPortraitPath = evaluateToken("PORTRAIT", pc); + assertEquals(inputPortraitPath, outputPortraitPath, "PORTRAIT token must not be encoded"); + } }