diff --git a/src/firefly/html/demo/ffapi-highlevel-test.html b/src/firefly/html/demo/ffapi-highlevel-test.html index 77ccc5b4bc..e2642bda31 100644 --- a/src/firefly/html/demo/ffapi-highlevel-test.html +++ b/src/firefly/html/demo/ffapi-highlevel-test.html @@ -332,6 +332,19 @@ 'image; box 280 200 72 72 0 # color=cyan text={right syntax}' ]; + /* + window.regionWithSpecialChars = [ + 'circle 202.4844693750341 47.23118060364845 13.9268922364868 # color=blue text={j2000 ; circle on ; J2000}', + 'J2000;box 202.4844693750341 47.23118060364845 0.0069268922364 0.0069268922364 0 # color=red text={box on # J2000 with radius ## 0.00198268922364868}', + 'circle 100.4844693750341 147.23118060364845 0.0239268922364868 # color=purple text="j2000 ; circle on # J2000"' + ]; + */ + window.regionWithSpecialChars = [ + 'circle 202.4844693750341 47.23118060364845 13.9268922364868 # color=cyan text={physical;circle ;; color=cyan}', + 'J2000;box 202.4844693750341 47.23118060364845 0.0069268922364 0.0069268922364 0 # color=red text={box on # J2000 with size ## 0.0069268922364}', + 'image;circle 100.4844693750341 147.23118060364845 10.0239268922364868 # color=#B8E986 text="image ; circle # color=#B8E986"' + ]; + window.moreRegionAry = [ 'image;ellipse 100 100 20p 40p 30p 60p 40p 80p 20 # color=green text={ellipseannulus 2}', @@ -359,7 +372,8 @@ document.getElementById('createRegionId').value = layerId; } - document.getElementById('regionLayerContent').value = window.smallregion.join('\n'); + document.getElementById('regionLayerContent').value = window.regionWithSpecialChars.join('\n'); + /* if (window.regionId === 0) { document.getElementById('regionLayerContent').value = window.emptyRegion; diff --git a/src/firefly/java/edu/caltech/ipac/util/RegionFactory.java b/src/firefly/java/edu/caltech/ipac/util/RegionFactory.java index fca6212df6..68e64c8cb6 100644 --- a/src/firefly/java/edu/caltech/ipac/util/RegionFactory.java +++ b/src/firefly/java/edu/caltech/ipac/util/RegionFactory.java @@ -36,6 +36,9 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Arrays; +import java.util.stream.Collectors; + /** * @author Trey Roby @@ -62,9 +65,11 @@ public static ParseRet processInput(LineGetter lineGetter) { try { List resultList= RegionFactory.parsePart(s, coordSys, g, allowHeader); if (allowHeader) { - g= RegionFactory.getGlobal(resultList, g); - coordSys= RegionFactory.getCsys(resultList, coordSys); + g = RegionFactory.getGlobal(resultList, g); } + coordSys= RegionFactory.getCsys(resultList, coordSys); //current coordinate applies to next region + //which has no coordinate set in front. + if (RegionFactory.containsRegion(resultList)) { allowHeader= false; RegionFactory.addAllRegions(resultList, regList); @@ -78,9 +83,54 @@ public static ParseRet processInput(LineGetter lineGetter) { return new ParseRet(regList,msgList); } + private static Boolean isInText; + private static String crtSeg; + private static int dLimitIdx; + private static List oneLineUnits; + private static String[] dLeft = {"{", "\"", "\'"}; + private static String[] dRight = {"}", "\"", "\'"}; + + + private static String[] splitStringBySeparator(String inString, String sep) { + List charsInString = Arrays.asList(inString.split("")); + + dLimitIdx = -1; + isInText = false; + crtSeg = ""; + oneLineUnits = new ArrayList(); + + charsInString + .stream() + .forEach( (c) -> { + if (c.equals(sep)) { + if (isInText) { + crtSeg += c; + } else { + if (crtSeg.length() > 0) { + oneLineUnits.add(crtSeg); + crtSeg = ""; + } + } + } else { + if (isInText) { + if (c.equals(dRight[dLimitIdx])) { + isInText = false; + } + } else { + dLimitIdx = Arrays.asList(dLeft).indexOf(c.toString()); + isInText = dLimitIdx >= 0; + } + crtSeg += c; + } + }); - + if (crtSeg.length() > 0) { + oneLineUnits.add(crtSeg); + crtSeg = ""; + } + return oneLineUnits.toArray(new String[oneLineUnits.size()]); + } public static List parsePart(String inString) throws RegParseException { return parsePart(inString, RegionCsys.IMAGE, new Global(new RegionOptions()),false); @@ -91,7 +141,7 @@ public static List parsePart(String inString, RegionCsys coor if (coordSys==null) coordSys= RegionCsys.IMAGE; RegionOptions globalOps= global!=null ? global.getOptions() : null; - String sAry[]= inString.split(";"); + String sAry[]= splitStringBySeparator(inString, ";"); // inString.split(";"); List retList= new ArrayList(4); for (String virtualLine: sAry) { @@ -116,7 +166,7 @@ public static List parsePart(String inString, RegionCsys coor Region region = null; String region_type= null; boolean include= true; - boolean isWorldCoord = false; + try { @@ -129,9 +179,7 @@ public static List parsePart(String inString, RegionCsys coor } if (isCoordSys(lineBegin)) { coordSys = getCoordSys(lineBegin); - if (allowHeader) { - retList.add(coordSys); - } + retList.add(coordSys); // a new coordinate setting continue; } else { @@ -144,8 +192,9 @@ public static List parsePart(String inString, RegionCsys coor } } } + + boolean isWorldCoord = isWorldCoords(coordSys); if (st.hasMoreToken()) { - isWorldCoord = isWorldCoords(coordSys); if (region_type.equals("vector") || region_type.equals("ruler") || @@ -589,6 +638,7 @@ private static RegOpsParseRet parseRegionOptionPlus(String s, RegionOptions fall StringTokenizer st= new StringTokenizer(s, " "); String workStr= s; retval.ops.setInclude(include); + while (st.hasMoreToken()) { String token = st.nextToken(); @@ -618,39 +668,45 @@ else if (token.toLowerCase().startsWith("offsety=")) { retval.ops.setOffsetY(parseInt(token, 0)); } else if (token.toLowerCase().startsWith("text=")) { - int endIdx= 5; + int endIdx= 0; String textPlus= workStr.substring(workStr.indexOf("text=")+5); String start= getStartCharOfString(textPlus); if (start!=null && textPlus.startsWith(start)) { String end= (start.equals("{")) ? "}" : start; - textPlus= textPlus.substring(1); - endIdx= textPlus.indexOf(end); - if (endIdx>-1) { - String textString= textPlus.substring(0,endIdx); + endIdx= textPlus.indexOf(end, 1); + if (endIdx > 0) { // end is found + String textString= textPlus.substring(1,endIdx); retval.ops.setText(textString); + endIdx++; + } else { // end is not found + endIdx = 1; } - else { - endIdx= 5; - } } - workStr= workStr.substring(endIdx+1).trim(); + + workStr = textPlus.substring(endIdx).trim(); + if (workStr.length() == 0) { + break; + } st= new StringTokenizer(workStr, " "); } else if (token.toLowerCase().startsWith("font=")) { - int endIdx= 5; + int endIdx= 0; String textPlus= workStr.substring(workStr.indexOf("font=")+5); if (textPlus.startsWith("\"")) { - textPlus= textPlus.substring(1); - endIdx= textPlus.indexOf("\""); - if (endIdx>-1) { - String fontString= textPlus.substring(0,endIdx); + endIdx = textPlus.indexOf("\"", 1); + if (endIdx > 0) { + String fontString = textPlus.substring(1, endIdx); retval.ops.setFont(new RegionFont(fontString)); - } - else { - endIdx= 5; + endIdx++; + } else { + endIdx = 1; } } - workStr= workStr.substring(endIdx+1).trim(); + workStr = textPlus.substring(endIdx).trim(); + if (workStr.length() == 0) { + break; + } + st= new StringTokenizer(workStr, " "); } else if (token.toLowerCase().startsWith("point=")) { diff --git a/src/firefly/js/visualize/region/RegionFactory.js b/src/firefly/js/visualize/region/RegionFactory.js index efe4103dc7..d622f39ef5 100644 --- a/src/firefly/js/visualize/region/RegionFactory.js +++ b/src/firefly/js/visualize/region/RegionFactory.js @@ -54,8 +54,11 @@ export class RegionFactory { * @returns {null} */ static parseRegionJson(regionData) { + + var globalOptions = Object.assign({}, makeRegionOptions({[regionPropsList.COORD]: defaultCoord})); + return regionData? regionData.reduce ( (prev, region, index) => { - const rg = RegionFactory.parsePart(region, index); + const rg = RegionFactory.parsePart(region, index, globalOptions); if (rg) { // skip comment line and no good line if (outputError(rg, region) === 0) prev.push(rg); @@ -74,37 +77,80 @@ export class RegionFactory { * @returns {array} an array of Region object */ static parseRegionDS9(regionData, bAllowHeader = true, stopAt) { - var regionLines = regionData.reduce ((prev, oneLine) => { - var resRegions = oneLine.split(';'); - - var crtCsys = null; - var preCsys = null; - var lastIdx = resRegions.length - 1; + const sep = ';'; + const dLeft = ['{', '"', '\'']; + const dRight = ['}', '"', '\'']; + + // split each string into a set of units which are separated by ';' with the consideration that the semicolon + // contained in the text or tag string is not counted as separator. + // Each unit could contain a coordinate string or a region description string + + var getStringUnits = (oneLine) => { + var isInText = false; // check if sep is inside a text string enclosed by a pair of delimiter + var crtSeg = ''; // current string unit + var dLimitIdx = -1; + var isLeftDelimiter = (c) => dLeft.findIndex((t) => (t === c)); + + var addNewUnitTo = (prev) => { + if (crtSeg.length > 0) { + prev.push(crtSeg.slice(0)); + crtSeg = ''; + } + }; - // combine coordinate and region definition of the same line - resRegions.forEach( (crtVal, index) => { - crtCsys = getRegionCoordSys(crtVal); + var incUnit = (v) => { + crtSeg = crtSeg.concat(v); + }; - if (crtCsys !== RegionCsys.UNDEFINED) { // coordinate string - if (index === lastIdx) { - prev = [...prev, crtVal]; - } else { - preCsys = crtCsys; + var lUnits = oneLine.split('').reduce((oneLineUnits, v) => { + if (v === sep) { + isInText ? incUnit(v) : addNewUnitTo(oneLineUnits); + } else { + if (isInText) { + if (v === dRight[dLimitIdx]) { + isInText = false; } } else { - // region string or the last one is coordinate - if (!preCsys) { - prev = [...prev, crtVal]; // no coordinate prior to current string - } else { - prev = [...prev, `${resRegions[index - 1]};${crtVal}`]; // combine with coordinate - } - preCsys = null; + dLimitIdx = isLeftDelimiter(v); + isInText = dLimitIdx >= 0; } - }); + incUnit(v); + } + return oneLineUnits; + }, []); - return prev; + if (crtSeg.length > 0) { + lUnits.push(crtSeg.slice()); + } + return lUnits; + }; + + // collect region lines and each line may contain coordinate, region description or both + var regionLines = regionData.reduce( (rLines, oneLine) => { + var units = getStringUnits(oneLine); + var lastIdx = units.length - 1; + var preCsys = null; // coordinate status of previous unit + + // combine the coordinate unit and region description unit from the same line into one string + var lines = units.reduce((unitSet, crtVal, index) => { + var crtCsys = getRegionCoordSys(crtVal); + + if (crtCsys !== RegionCsys.UNDEFINED) { // current string is coordinate unit + if (index === lastIdx) { + unitSet = [...unitSet, crtVal]; + } else { + preCsys = crtCsys; + } + } else { + unitSet = [...unitSet, (preCsys ? `${units[index - 1]};${crtVal}` : crtVal)]; + preCsys = null; + } + return unitSet; }, []); + return [...rLines,...lines]; + }, []); + var globalOptions = Object.assign({}, makeRegionOptions({[regionPropsList.COORD]: defaultCoord})); return regionLines.reduce ( (prev, region, index) => { @@ -131,7 +177,7 @@ export class RegionFactory { } /** * parsePart parses the region data of JSON result (one item from RegionData array) - * @param regionStr + * @param regionStr each string is either like coordinate;region_description or region_descrption * @param index line number shown in message * @param globalOptions * @param bAllowHeader @@ -176,23 +222,28 @@ export class RegionFactory { // check if coordination system (from RegionDS9) tmpAry = regionStr.split(';'); + var csys = getRegionCoordSys(tmpAry[0]); // test the split first string - if (tmpAry.length <= 1) { - var csys = getRegionCoordSys(tmpAry[0]); + if (csys !== RegionCsys.UNDEFINED) { + if (globalOptions) globalOptions.coordSys = tmpAry[0].toLowerCase(); - if (csys !== RegionCsys.UNDEFINED && globalOptions) { - globalOptions.coordSys = tmpAry[0].toLowerCase(); + if (tmpAry.length <= 1) { // pure coordinate string return null; } + if (tmpAry.length > 2) { // description contain ';' + tmpAry = [tmpAry[0], tmpAry.slice(1).join(';')]; // coordinate and description + } + } else if (tmpAry.length >= 2) { // no coordinate and description contain ';' + tmpAry = [tmpAry.join(';')]; } - // check if string contains coordinate, description and property portions - // -- regionCoord -- - if (tmpAry.length > 1) { + + if (tmpAry.length > 1) { // coordinate and description regionCoord = tmpAry[0].trim(); - tmpAry.shift(); - bCoord = true; // coordinate is defined in this line - } else { + tmpAry.shift(); // remove the coordinate element + + bCoord = true; + } else { // description only // default coordinate is PHYSICAL in case not specified regionCoord = globalOptions && has(globalOptions, regionPropsList.COORD) ? globalOptions[regionPropsList.COORD] : defaultCoord; @@ -630,7 +681,7 @@ export class RegionFactory { (c !== RegionCsys.DETECTOR) ); var makePt = (vx, vy, cs) => { - if (vx.unit === RegionValueUnit.IMAGE_PIXEL) { + if (vx.unit === RegionValueUnit.IMAGE_PIXEL || vx.unit === RegionValueUnit.SCREEN_PIXEL) { return makeImagePt(vx.value, vy.value); } else { return makeWorldPt(vx.value, vy.value, this.parse_coordinate(cs));