blob: 998b0ee7762d6b21db33f7e96e8d3d358a680648 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*/
package org.apache.poi.sl.draw.geom;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
import org.apache.poi.util.Internal;
@Internal
class PresetParser {
enum Mode {
FILE(PresetParser::updateFile),
SHAPE_LST(PresetParser::updateShapeList),
SHAPE(PresetParser::updateShape),
GUIDE_LST(PresetParser::updateGuideList),
AH_LST(PresetParser::updateAhList),
CXN_LST(PresetParser::updateCxnList),
PATH_LST(PresetParser::updatePathLst),
PATH(PresetParser::updatePath);
interface Handler {
void update(PresetParser parser, XMLStreamReader sr) throws XMLStreamException;
}
final Handler handler;
Mode(Handler handler) {
this.handler = handler;
}
}
private static final Logger LOG = LogManager.getLogger(PresetParser.class);
private Mode mode;
private final Map<String, CustomGeometry> geom = new HashMap<>();
private CustomGeometry customGeometry;
private boolean useAdjustValue;
private Path path;
PresetParser(Mode mode) {
this.mode = mode;
if (mode == Mode.SHAPE) {
customGeometry = new CustomGeometry();
geom.put("custom", customGeometry);
}
}
void parse(XMLStreamReader sr) throws XMLStreamException {
while (sr.hasNext()) {
switch (sr.next()) {
case XMLStreamConstants.START_ELEMENT:
mode.handler.update(this, sr);
break;
case XMLStreamConstants.END_ELEMENT:
endContext();
break;
case XMLStreamConstants.END_DOCUMENT:
return;
default:
break;
}
}
}
Map<String, CustomGeometry> getGeom() {
return geom;
}
private void updateFile(XMLStreamReader sr) {
final String name = sr.getLocalName();
assert("presetShapeDefinitons".equals(name));
mode = Mode.SHAPE_LST;
}
private void updateShapeList(XMLStreamReader sr) {
final String name = sr.getLocalName();
customGeometry = new CustomGeometry();
if (geom.containsKey(name)) {
LOG.atWarn().log("Duplicate definition of {}", name);
}
geom.put(name, customGeometry);
mode = Mode.SHAPE;
}
private void updateShape(XMLStreamReader sr) throws XMLStreamException {
final String name = sr.getLocalName();
switch (name) {
case "avLst":
useAdjustValue = true;
mode = Mode.GUIDE_LST;
break;
case "gdLst":
useAdjustValue = false;
mode = Mode.GUIDE_LST;
break;
case "ahLst":
mode = Mode.AH_LST;
break;
case "cxnLst":
mode = Mode.CXN_LST;
break;
case "rect":
addRectangle(sr);
break;
case "pathLst":
mode = Mode.PATH_LST;
break;
}
}
private void updateGuideList(XMLStreamReader sr) throws XMLStreamException {
final String name = sr.getLocalName();
assert("gd".equals(name));
final Guide gd;
if (useAdjustValue) {
customGeometry.addAdjustGuide((AdjustValue)(gd = new AdjustValue()));
} else {
customGeometry.addGeomGuide(gd = new Guide());
}
parseAttributes(sr, (key,val) -> {
switch (key) {
case "name":
// CollapsedStringAdapter
gd.setName(collapseString(val));
break;
case "fmla":
gd.setFmla(val);
break;
}
});
int tag = nextTag(sr);
assert(tag == XMLStreamConstants.END_ELEMENT);
}
private void updateAhList(XMLStreamReader sr) throws XMLStreamException {
String name = sr.getLocalName();
switch (name) {
case "ahXY":
addXY(sr);
break;
case "ahPolar":
addPolar(sr);
break;
}
}
private void addXY(XMLStreamReader sr) throws XMLStreamException {
XYAdjustHandle ahXY = new XYAdjustHandle();
customGeometry.addAdjustHandle(ahXY);
parseAttributes(sr, (key, val) -> {
switch (key) {
case "gdRefX":
ahXY.setGdRefX(collapseString(val));
break;
case "minX":
ahXY.setMinX(val);
break;
case "maxX":
ahXY.setMaxX(val);
break;
case "gdRefY":
ahXY.setGdRefY(collapseString(val));
break;
case "minY":
ahXY.setMinY(val);
break;
case "maxY":
ahXY.setMaxY(val);
break;
}
});
ahXY.setPos(parsePosPoint(sr));
}
private void addPolar(XMLStreamReader sr) throws XMLStreamException {
PolarAdjustHandle ahPolar = new PolarAdjustHandle();
customGeometry.addAdjustHandle(ahPolar);
parseAttributes(sr, (key, val) -> {
switch (key) {
case "gdRefR":
ahPolar.setGdRefR(collapseString(val));
break;
case "minR":
ahPolar.setMinR(val);
break;
case "maxR":
ahPolar.setMaxR(val);
break;
case "gdRefAng":
ahPolar.setGdRefAng(collapseString(val));
break;
case "minAng":
ahPolar.setMinAng(val);
break;
case "maxAng":
ahPolar.setMaxAng(val);
break;
}
});
ahPolar.setPos(parsePosPoint(sr));
}
private void updateCxnList(XMLStreamReader sr) throws XMLStreamException {
String name = sr.getLocalName();
assert("cxn".equals(name));
ConnectionSite cxn = new ConnectionSite();
customGeometry.addConnectionSite(cxn);
parseAttributes(sr, (key, val) -> {
if ("ang".equals(key)) {
cxn.setAng(val);
}
});
cxn.setPos(parsePosPoint(sr));
}
private void updatePathLst(XMLStreamReader sr) {
String name = sr.getLocalName();
assert("path".equals(name));
path = new Path();
customGeometry.addPath(path);
parseAttributes(sr, (key, val) -> {
switch (key) {
case "w":
path.setW(Long.parseLong(val));
break;
case "h":
path.setH(Long.parseLong(val));
break;
case "fill":
path.setFill(mapFill(val));
break;
case "stroke":
path.setStroke(Boolean.parseBoolean(val));
break;
case "extrusionOk":
path.setExtrusionOk(Boolean.parseBoolean(val));
break;
}
});
mode = Mode.PATH;
}
private static PaintModifier mapFill(String fill) {
switch (fill) {
default:
case "none":
return PaintModifier.NONE;
case "norm":
return PaintModifier.NORM;
case "lighten":
return PaintModifier.LIGHTEN;
case "lightenLess":
return PaintModifier.LIGHTEN_LESS;
case "darken":
return PaintModifier.DARKEN;
case "darkenLess":
return PaintModifier.DARKEN_LESS;
}
}
private void updatePath(XMLStreamReader sr) throws XMLStreamException {
String name = sr.getLocalName();
switch (name) {
case "close":
closePath(sr);
break;
case "moveTo":
moveTo(sr);
break;
case "lnTo":
lineTo(sr);
break;
case "arcTo":
arcTo(sr);
break;
case "quadBezTo":
quadBezTo(sr);
break;
case "cubicBezTo":
cubicBezTo(sr);
break;
}
}
private void closePath(XMLStreamReader sr) throws XMLStreamException {
path.addCommand(new ClosePathCommand());
int tag = nextTag(sr);
assert(tag == XMLStreamConstants.END_ELEMENT);
}
private void moveTo(XMLStreamReader sr) throws XMLStreamException {
MoveToCommand cmd = new MoveToCommand();
path.addCommand(cmd);
AdjustPoint pt = parsePtPoint(sr, true);
assert(pt != null);
cmd.setPt(pt);
}
private void lineTo(XMLStreamReader sr) throws XMLStreamException {
LineToCommand cmd = new LineToCommand();
path.addCommand(cmd);
AdjustPoint pt = parsePtPoint(sr, true);
assert(pt != null);
cmd.setPt(pt);
}
private void arcTo(XMLStreamReader sr) throws XMLStreamException {
ArcToCommand cmd = new ArcToCommand();
path.addCommand(cmd);
parseAttributes(sr, (key, val) -> {
switch (key) {
case "wR":
cmd.setWR(val);
break;
case "hR":
cmd.setHR(val);
break;
case "stAng":
cmd.setStAng(val);
break;
case "swAng":
cmd.setSwAng(val);
break;
}
});
int tag = nextTag(sr);
assert (tag == XMLStreamConstants.END_ELEMENT);
}
private void quadBezTo(XMLStreamReader sr) throws XMLStreamException {
QuadToCommand cmd = new QuadToCommand();
path.addCommand(cmd);
AdjustPoint pt1 = parsePtPoint(sr, false);
AdjustPoint pt2 = parsePtPoint(sr, true);
assert (pt1 != null && pt2 != null);
cmd.setPt1(pt1);
cmd.setPt2(pt2);
}
private void cubicBezTo(XMLStreamReader sr) throws XMLStreamException {
CurveToCommand cmd = new CurveToCommand();
path.addCommand(cmd);
AdjustPoint pt1 = parsePtPoint(sr, false);
AdjustPoint pt2 = parsePtPoint(sr, false);
AdjustPoint pt3 = parsePtPoint(sr, true);
assert (pt1 != null && pt2 != null && pt3 != null);
cmd.setPt1(pt1);
cmd.setPt2(pt2);
cmd.setPt3(pt3);
}
private void addRectangle(XMLStreamReader sr) throws XMLStreamException {
String[] ltrb = new String[4];
parseAttributes(sr, (key,val) -> {
switch (key) {
case "l":
ltrb[0] = val;
break;
case "t":
ltrb[1] = val;
break;
case "r":
ltrb[2] = val;
break;
case "b":
ltrb[3] = val;
break;
}
});
customGeometry.setTextBounds(ltrb[0],ltrb[1],ltrb[2],ltrb[3]);
int tag = nextTag(sr);
assert(tag == XMLStreamConstants.END_ELEMENT);
}
private void endContext() {
switch (mode) {
case FILE:
case SHAPE_LST:
mode = Mode.FILE;
break;
case SHAPE:
mode = Mode.SHAPE_LST;
break;
case CXN_LST:
case AH_LST:
case GUIDE_LST:
case PATH_LST:
useAdjustValue = false;
path = null;
mode = Mode.SHAPE;
break;
case PATH:
path = null;
mode = Mode.PATH_LST;
break;
}
}
private AdjustPoint parsePosPoint(XMLStreamReader sr) throws XMLStreamException {
return parseAdjPoint(sr, true, "pos");
}
private AdjustPoint parsePtPoint(XMLStreamReader sr, boolean closeOuter) throws XMLStreamException {
return parseAdjPoint(sr, closeOuter, "pt");
}
private AdjustPoint parseAdjPoint(XMLStreamReader sr, boolean closeOuter, String name) throws XMLStreamException {
int tag = nextTag(sr);
if (tag == XMLStreamConstants.END_ELEMENT) {
return null;
}
assert (name.equals(sr.getLocalName()));
AdjustPoint pos = new AdjustPoint();
parseAttributes(sr, (key, val) -> {
switch (key) {
case "x":
pos.setX(val);
break;
case "y":
pos.setY(val);
break;
}
});
tag = nextTag(sr);
assert(tag == XMLStreamConstants.END_ELEMENT);
if (closeOuter) {
tag = nextTag(sr);
assert(tag == XMLStreamConstants.END_ELEMENT);
}
return pos;
}
private void parseAttributes(XMLStreamReader sr, BiConsumer<String,String> c) {
for (int i=0; i<sr.getAttributeCount(); i++) {
c.accept(sr.getAttributeLocalName(i), sr.getAttributeValue(i));
}
}
/**
* Reimplement {@link XMLStreamReader#nextTag()} because of differences of XmlBeans and Xerces XMLStreamReader.
* XmlBeans doesn't return the END_ELEMENT on nextTag()
*
* @param sr the stream reader
* @return the next tag type (START_ELEMENT, END_ELEMENT, END_DOCUMENT)
* @throws XMLStreamException if reading the next tag fails
*/
private static int nextTag(XMLStreamReader sr) throws XMLStreamException {
int tag;
do {
tag = sr.next();
} while (
tag != XMLStreamConstants.START_ELEMENT &&
tag != XMLStreamConstants.END_ELEMENT &&
tag != XMLStreamConstants.END_DOCUMENT
);
return tag;
}
/**
* Removes leading and trailing whitespaces of the string given as the parameter, then truncate any
* sequence of tab, CR, LF, and SP by a single whitespace character ' '.
*/
private static String collapseString(String text) {
if (text==null) {
return null;
}
int len = text.length();
// most of the texts are already in the collapsed form. so look for the first whitespace in the hope that we
// will never see it.
int s;
for (s=0; s<len; s++) {
if (isWhiteSpace(text.charAt(s))) {
break;
}
}
if (s == len) {
// the input happens to be already collapsed.
return text;
}
// we now know that the input contains spaces. let's sit down and do the collapsing normally.
// allocate enough size to avoid re-allocation
StringBuilder result = new StringBuilder(len);
if (s != 0) {
for(int i=0; i<s; i++) {
result.append(text.charAt(i));
}
result.append(' ');
}
boolean inStripMode = true;
for (int i = s+1; i < len; i++) {
char ch = text.charAt(i);
boolean b = isWhiteSpace(ch);
if (inStripMode && b) {
// skip this character
continue;
}
inStripMode = b;
result.append(inStripMode ? ' ' : ch);
}
// remove trailing whitespaces
len = result.length();
if (len > 0 && result.charAt(len - 1) == ' ') {
result.setLength(len - 1);
}
// whitespaces are already collapsed, so all we have to do is
// to remove the last one character if it's a whitespace.
return result.toString();
}
/** returns true if the specified char is a white space character. */
private static boolean isWhiteSpace(char ch) {
return ch == 0x9 || ch == 0xA || ch == 0xD || ch == 0x20;
}
}