/*
* This file is part of LaTeXDraw.
* Copyright (c) 2005-2017 Arnaud BLOUIN
* LaTeXDraw 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 2 of the License, or (at your option) any later version.
* LaTeXDraw is distributed 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.
*/
package net.sf.latexdraw.parsers.svg;
import java.text.ParseException;
import java.util.Objects;
import net.sf.latexdraw.badaboom.BadaboomCollector;
import net.sf.latexdraw.models.interfaces.shape.Color;
import net.sf.latexdraw.parsers.svg.parsers.CSSStyleParser;
import net.sf.latexdraw.parsers.svg.parsers.SVGLengthParser;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
import org.w3c.dom.UserDataHandler;
/**
* Defines an SVG element.
* @author Arnaud BLOUIN
*/
public abstract class SVGElement implements Element, Cloneable {
/** The attributes of the element. @since 0.1 */
protected final SVGNamedNodeMap attributes;
/** The children of this element. @since 0.1 */
protected final SVGNodeList children;
/** The parent SVGElement of this element. @since 0.1 */
protected SVGElement parent;
/** The name of the tag. @since 0.1 */
protected String name;
/** The list of transformations which concern the element. @since 0.1 */
protected SVGTransformList transform;
/** The list of the CSS styles of the SVG attribute style. @since 0.1 */
protected CSSStyleList stylesCSS;
/** The document containing the element. @since 0.1 */
protected SVGDocument ownerDocument;
/**
* The constructor by default.
* @since 0.1
*/
protected SVGElement() {
super();
children = new SVGNodeList();
attributes = new SVGNamedNodeMap();
transform = null;
stylesCSS = null;
parent = null;
name = null;
ownerDocument = null;
}
/**
* Creates an simple SVGElement with the owner document.
* @param owner The owner document.
* @since 0.1
*/
protected SVGElement(final SVGDocument owner) {
this();
ownerDocument = Objects.requireNonNull(owner);
}
/**
* The constructor using a node in order to be initialised.
* @param n The node.
* @throws MalformedSVGDocument If the element is not well formed.
* @since 0.1
*/
protected SVGElement(final Node n) throws MalformedSVGDocument {
this(n, null);
}
/**
* The constructor using a node to create the SVG element and an SVG element to be its parent.
* @param n The node.
* @param p The parent SVG element.
* @throws MalformedSVGDocument If the element is not well formed.
* @since 0.1
*/
protected SVGElement(final Node n, final SVGElement p) throws MalformedSVGDocument {
this();
if(n==null)
throw new IllegalArgumentException();
if(p!=null) {
ownerDocument = p.getOwnerDocument();
setParent(p);
}
setAttributes(n);
setNodeValue(n.getNodeValue());
setNodeName(n.getNodeName());
String v = getAttribute(getUsablePrefix()+SVGAttributes.SVG_TRANSFORM);
if(v!=null) {
transform = new SVGTransformList();
transform.addTransformations(getAttribute(getUsablePrefix()+SVGAttributes.SVG_TRANSFORM));
}
v = getAttribute(getUsablePrefix()+SVGAttributes.SVG_STYLE);
if(v!=null) {
stylesCSS = new CSSStyleList();
try { new CSSStyleParser(v, stylesCSS).parse(); }
catch(final ParseException e) { BadaboomCollector.INSTANCE.add(e); }
}
if(!checkAttributes())
throw new MalformedSVGDocument();
final NodeList nl = n.getChildNodes();
int i;
final int size = nl.getLength();
if(size==1 && ("#text".equals(nl.item(0).getNodeName()) || "#cdata-section".equals(nl.item(0).getNodeName())))//$NON-NLS-1$//$NON-NLS-2$
setTextContent(n.getTextContent());
for(i=0; i<size; i++)
SVGElementsFactory.INSTANCE.createSVGElement(nl.item(i), this);
}
/**
* Copies the attributes of the given node.
* @param n The node to copy.
*/
protected void setAttributes(final Node n) {
if(n==null || n.getAttributes()==null)
return;
final NamedNodeMap nnm = n.getAttributes();
for(int i=0, size=nnm.getLength(); i<size; i++)
attributes.getAttributes().add(new SVGAttr(nnm.item(i).getNodeName(), nnm.item(i).getNodeValue(), this));
}
/**
* Allows to get the root of the SVG document.
* @return The root.
* @since 0.1
*/
public SVGElement getRootElement() {
if(parent==null)
return this;
return parent.getRootElement();
}
/**
* @return the parent.
* @since 0.1
*/
public SVGElement getParent() {
return parent;
}
/**
* @param parent the parent to set.
* @since 0.1
*/
public void setParent(final SVGElement parent) {
if(this.parent!=parent) {
if(this.parent!=null)
this.parent.children.getNodes().remove(this);
this.parent = parent;
if(this.parent!=null && !this.parent.children.getNodes().contains(this))
this.parent.children.getNodes().add(this);
}
}
@Override
public NamedNodeMap getAttributes() {
return attributes;
}
@Override
public String toString() {
final StringBuilder str = new StringBuilder().append('[');
int i;
final int size = children.getLength();
str.append("name=").append(name).append(',');//$NON-NLS-1$
if(!hasChildNodes())
str.append("textContent=").append(getTextContent()).append(',');//$NON-NLS-1$
str.append("attributes=");//$NON-NLS-1$
if(attributes!=null)
str.append(attributes);
str.append(", children={");//$NON-NLS-1$
for(i=0; i<size-1; i++)
str.append(children.item(i)).append(',');
if(size>0)
str.append(children.getNodes().get(children.getNodes().size()-1));
str.append('}');
return str.append(']').toString();
}
@Override
public String getNodeName() {
return name;
}
/**
* Sets the name of the SVG element.
* @param name Its new name.
*/
public void setNodeName(final String name) {
this.name = name;
}
@Override
public String getAttribute(final String nameAttr) {
if(attributes==null || nameAttr==null)
return null;
final Node n = attributes.getNamedItem(nameAttr);
return n==null ? null : n.getNodeValue();
}
@Override
public Attr getAttributeNode(final String nameAttr) {
if(attributes==null)
return null;
return (Attr)attributes.getNamedItem(nameAttr);
}
@Override
public String getTagName() {
return name;
}
@Override
public Node appendChild(final Node newChild) {
if(!(newChild instanceof SVGElement))
throw new DOMException(DOMException.TYPE_MISMATCH_ERR, "SVGElement excepted here.");//$NON-NLS-1$
if(children.getNodes().contains(newChild))
children.getNodes().remove(newChild);
children.getNodes().add((SVGElement)newChild);
((SVGElement)newChild).setParent(this);
return newChild;
}
@Override
public NodeList getChildNodes() {
return children;
}
@Override
public Node getFirstChild() {
return children.getNodes()==null || children.getNodes().isEmpty() ? null : children.getNodes().get(0);
}
@Override
public Node getLastChild() {
return children.getNodes()==null || children.getNodes().isEmpty() ? null : children.getNodes().get(children.getNodes().size()-1);
}
@Override
public short getNodeType() {
return Node.ELEMENT_NODE;
}
@Override
public SVGDocument getOwnerDocument() {
return ownerDocument;
}
@Override
public Node getParentNode() {
return parent;
}
@Override
public boolean hasAttributes() {
return attributes!=null && attributes.getAttributes()!=null && !attributes.getAttributes().isEmpty();
}
@Override
public boolean hasChildNodes() {
return children.getNodes()!=null && !children.getNodes().isEmpty();
}
@Override
public Node insertBefore(final Node newChild, final Node refChild) {
boolean ok = false;
if(newChild!=null && refChild!=null) {
final int pos = children.getNodes().indexOf(refChild);
if(pos!=-1 && newChild instanceof SVGElement) {
children.getNodes().add(pos, (SVGElement)newChild);
ok = true;
}
}
return ok ? newChild : null;
}
@Override
public boolean isEqualNode(final Node node) {
if(!(node instanceof SVGElement))
return false;
final SVGElement elt = (SVGElement)node;
final String uri = lookupNamespaceURI(null);
final String val = getNodeValue();
final boolean valEq = val==null ? elt.getNodeValue()==null : val.equals(elt.getNodeValue());
final boolean uriEq = uri==null ? elt.lookupNamespaceURI(null)==null : uri.equals(elt.lookupNamespaceURI(null));
final boolean attrEq = attributes==null ? elt.attributes==null : attributes.equals(elt.attributes);
return name.equals(elt.name) && getUsablePrefix().equals(elt.getUsablePrefix()) && uriEq && valEq && attrEq;
}
@Override
public boolean isSameNode(final Node other) {
return other!=null && other==this;
}
@Override
public Node removeChild(final Node oldChild) {
boolean ok = false;
if(oldChild!=null)
ok = children.getNodes().remove(oldChild);
return ok ? oldChild : null;
}
@Override
public void setTextContent(final String textContent) {
if(textContent==null)
throw new DOMException(DOMException.DOMSTRING_SIZE_ERR, "textContent is null.");//$NON-NLS-1$
appendChild(new SVGText(textContent, getOwnerDocument()));
}
@Override
public String lookupPrefix(final String namespaceURI) {
if(namespaceURI == null)
return null;
String pref = null;
final String xmlns = "xmlns"; //$NON-NLS-1$
if(attributes!=null) {
int i=0;
final int size = attributes.getLength();
boolean again = true;
while(i<size && again) {
final SVGAttr attr = attributes.getAttributes().get(i);
final String attrName = attr.getName();
if(attrName!=null && attrName.startsWith(xmlns) && namespaceURI.equals(attr.getValue())) {
final int index = attrName.indexOf(':');
pref = index==-1 ? "" : attrName.substring(index+1); //$NON-NLS-1$
again = false;
}
else
i++;
}
}
if(pref!=null)
return pref;
if(getParentNode()==null)
return null;
return getParentNode().lookupPrefix(namespaceURI);
}
@Override
public boolean hasAttribute(final String nameAttr) {
return attributes!=null && attributes.getLength()>0;
}
@Override
public String getNamespaceURI() {
return lookupNamespaceURI(getPrefix());
}
@Override
public String lookupNamespaceURI(final String pref) {
String uri = null;
if(attributes!=null)
if(pref==null) {
int i=0;
final int size = attributes.getLength();
boolean again = true;
final String xmlns = "xmlns";//$NON-NLS-1$
while(i<size && again) {
final SVGAttr attr = attributes.getAttributes().get(i);
final String attrName = attr.getName();
if(xmlns.equals(attrName)) {
uri = attr.getNodeValue();
again = false;
}
else
i++;
}
}
else {
int i=0;
final int size = attributes.getLength();
boolean again = true;
final String xmlns = "xmlns:";//$NON-NLS-1$
while(i<size && again) {
final SVGAttr attr = attributes.getAttributes().get(i);
final String attrName = attr.getName();
if(attrName!=null && attrName.startsWith(xmlns) && pref.equals(attrName.substring(xmlns.length()))) {
uri = attr.getNodeValue();
again = false;
}
else
i++;
}
}
if(uri!=null)
return uri;
if(getParentNode()==null)
return null;
return parent.lookupNamespaceURI(pref);
}
@Override
public String getPrefix() {
if(getNodeName()==null)
return null;
final int index = getNodeName().indexOf(':');
if(index!=-1)
return getNodeName().substring(0, index);
return null;
}
/**
* @return The prefix followed by Character ':'. This method aims at simplifying the use of the prefix.
*/
public String getUsablePrefix() {
final String prefix = getPrefix();
return prefix==null || prefix.isEmpty() ? "" : prefix + ':'; //$NON-NLS-1$
}
/**
* Sets the transformation of the elements. Removes previous transformations.
* Should not be called directly; call setAttribute instead.
* @param transformation The transformation to set.
* @since 3.0
*/
private void setTransformation(final String transformation) {
if(transform==null)
transform = new SVGTransformList();
else
transform.clear();
transform.addTransformations(getAttribute(getUsablePrefix()+SVGAttributes.SVG_TRANSFORM));
}
@Override
public void setAttribute(final String name, final String value) {
if(value==null || name==null)
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "Invalid name or/and value");//$NON-NLS-1$
attributes.setNamedItem(new SVGAttr(name, value, this));
if(SVGAttributes.SVG_TRANSFORM.equals(name))
setTransformation(value);
}
@Override
public Node getNextSibling() {
if(parent==null)
return null;
final SVGNodeList nl = (SVGNodeList)parent.getChildNodes();
final int index = nl.getNodes().indexOf(this);
if(index==-1)
return null;
return index+1>=nl.getLength() ? null : nl.getNodes().get(index+1);
}
@Override
public Node getPreviousSibling() {
if(parent==null)
return null;
final SVGNodeList nl = (SVGNodeList)parent.getChildNodes();
final int index = nl.getNodes().indexOf(this);
if(index==-1)
return null;
return index-1<0 ? null : nl.getNodes().get(index+1);
}
@Override
public NodeList getElementsByTagName(final String nameElt) {
if("*".equals(nameElt)) //$NON-NLS-1$
return getChildNodes();
final SVGNodeList nl = new SVGNodeList();
if(nameElt!=null) {
final NodeList nl2 = getChildNodes();
int i;
final int size = nl2.getLength();
Node n;
for(i=0; i<size; i++) {
n = nl2.item(i);
if(n instanceof SVGElement && nameElt.equals(n.getNodeName()))
nl.getNodes().add((SVGElement)n);
}
}
return nl;
}
@Override
public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
final String all = "*"; //$NON-NLS-1$
if(all.equals(namespaceURI))
return getElementsByTagName(localName);
final SVGNodeList nl = new SVGNodeList();
if(namespaceURI!=null && localName!=null) {
final boolean getAll = all.equals(localName);
final NodeList nl2 = getChildNodes();
int i;
final int size = nl2.getLength();
Node n;
for(i=0; i<size; i++) {
n = nl2.item(i);
if(n instanceof SVGElement && namespaceURI.equals(n.getNamespaceURI()) && (getAll || n.getNodeName().endsWith(localName)))
nl.getNodes().add((SVGElement)n);
}
}
return nl;
}
@Override
public String getTextContent() {
final NodeList nl = getElementsByTagName(SVGText.TEXT_NODE_NAME);
final StringBuilder buf = new StringBuilder();
for(int i=0, size=nl.getLength(); i<size; i++)
buf.append(((SVGText)nl.item(i)).getData());
return buf.toString();
}
@Override
public String getLocalName() {
return name.replaceAll(getUsablePrefix(), ""); //$NON-NLS-1$
}
@Override
public void removeAttribute(final String nameAttr) {
try {
if(nameAttr!=null && attributes!=null)
attributes.removeNamedItem(nameAttr);
}
catch(final DOMException e) { /* Nothing to do. */ }
}
@Override
public void removeAttributeNS(final String namespaceURI, final String localName)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Attr removeAttributeNode(final Attr oldAttr)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Attr setAttributeNode(final Attr newAttr)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Attr setAttributeNodeNS(final Attr newAttr)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void setIdAttribute(final String name, final boolean isId)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void setIdAttributeNS(final String namespaceURI, final String localName, final boolean isId)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void setIdAttributeNode(final Attr idAttr, final boolean isId)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Node cloneNode(final boolean deep)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public short compareDocumentPosition(final Node other)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public String getBaseURI()
{ return ""; } //$NON-NLS-1$
@Override
public TypeInfo getSchemaTypeInfo()
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Object getFeature(final String feature, final String version)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Object getUserData(final String key)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public boolean isDefaultNamespace(final String namespaceURI)
{ return SVGDocument.SVG_NAMESPACE.equals(namespaceURI); }
@Override
public boolean isSupported(final String feature, final String version)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void normalize()
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Node replaceChild(final Node newChild, final Node oldChild)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public Object setUserData(final String key, final Object data, final UserDataHandler handler)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public void setPrefix(final String prefix)
{ throw new DOMException(DOMException.INVALID_ACCESS_ERR, SVGDocument.ACTION_NOT_IMPLEMENTED); }
@Override
public String getNodeValue()
{ return null; }
@Override
public void setNodeValue(final String nodeValue)
{ /* No value. */ }
/**
* Returns the prefix of the given namespace URI with the ':' character after or an empty string
* if no prefix is found.
* @param namespaceURI The URI to look for.
* @return the prefix followed by ':' or an empty string.
* @since 0.1
*/
public String lookupPrefixUsable(final String namespaceURI) {
String pref = lookupPrefix(namespaceURI);
if(pref==null)
pref = "";//$NON-NLS-1$
else
pref += ":";//$NON-NLS-1$
return pref;
}
/**
* Check if the SVG element is valid according to the SVG specification.
* @return True if valid.
* @since 0.1
*/
public abstract boolean checkAttributes();
/**
* According to the SVG specification, some attributes may lead to disables rendering of the element (e.g. width=0,
* height=0,...). This method checks these conditions depending of the SVG element.
* @return True if the element can be rendered.
* @since 0.1
*/
public abstract boolean enableRendering();
@Override
public Attr getAttributeNodeNS(final String namespaceURI, final String localName) {
if(localName==null)
return null;
return getAttributeNode(lookupPrefixUsable(namespaceURI) + localName);
}
@Override
public String getAttributeNS(final String namespaceURI, final String localName) {
if(localName==null)
return null;
return getAttribute(lookupPrefixUsable(namespaceURI) + localName);
}
@Override
public boolean hasAttributeNS(final String namespaceURI, final String localName) {
return getAttributeNodeNS(namespaceURI, localName)!=null;
}
/**
* @return The root element of the SVG document: an SVGSVGElement.
* @since 0.1
*/
public SVGSVGElement getSVGRoot() {
final SVGElement e = getRootElement();
if(e instanceof SVGSVGElement)
return (SVGSVGElement)e;
return null;
}
/**
* Allows to get a definition (a tag in the 'def' part) with the identifier 'id'.
* @param id The identifier of the wanted definition.
* @return The definition or null.
* @since 0.1
*/
public SVGElement getDef(final String id) {
final SVGSVGElement root = getSVGRoot();
if(root==null)
return null;
return root.getDefs().getDef(id);
}
/**
* @return The identifier of the SVGElement.
* @since 0.1
*/
public String getId() {
return getAttribute(getUsablePrefix()+SVGAttributes.SVG_ID);
}
/**
* Allow to get a set of children having the name 'nodeName'
* @param nodeName The name of the wanted nodes.
* @return The set of nodes (may be empty but not null).
* @since 0.1
*/
public SVGNodeList getChildren(final String nodeName) {
final SVGNodeList nl = new SVGNodeList();
final NodeList nl2 = getChildNodes();
for(int i=0, size = nl2.getLength(); i<size; i++)
if(nl2.item(i).getNodeName().equals(nodeName))
nl.getNodes().add((SVGElement)nl2.item(i));
return nl;
}
/**
* Sets the stroke width of the SVG shape.
* @param strokeW The new stroke width (must be greater than 0).
* @since 0.1
*/
public void setStrokeWidth(final double strokeW) {
if(strokeW>0)
setAttribute(getUsablePrefix()+SVGAttributes.SVG_STROKE_WIDTH, String.valueOf(strokeW));
}
/**
* Sets the line cap of the stroke of the SVG shape.
* @param svgLineCap The line cap to set. Must be SVG_LINECAP_VALUE_BUTT or SVG_LINECAP_VALUE_ROUND
* or SVG_LINECAP_VALUE_SQUARE.
* @since 0.2
*/
public void setStrokeLineCap(final String svgLineCap) {
if(SVGAttributes.SVG_LINECAP_VALUE_BUTT.equals(svgLineCap) || SVGAttributes.SVG_LINECAP_VALUE_ROUND.equals(svgLineCap) ||
SVGAttributes.SVG_LINECAP_VALUE_SQUARE.equals(svgLineCap))
setAttribute(getUsablePrefix()+SVGAttributes.SVG_STROKE_LINECAP, svgLineCap);
}
/**
* Sets the miter level of the stroke.
* @param miterLevel The miter level to set. Must be greater than or equal to 1.
* @since 0.2
*/
public void setStrokeMiterLevel(final double miterLevel) {
if(miterLevel>=1)
setAttribute(SVGAttributes.SVG_STROKE_MITERLIMIT, String.valueOf(miterLevel));
}
/**
* Sets the dash offset of the stroke.
* @param dashOffset The dash offset to set.
* @since 0.2
*/
public void setStrokeDashOffset(final double dashOffset) {
setAttribute(SVGAttributes.SVG_STROKE_DASHOFFSET, String.valueOf(dashOffset));
}
/**
* Sets the dash array of the stroke.
* @param dashArray The dash array to set. Must not be null.
* @since 0.2
*/
public void setStrokeDashArray(final String dashArray) {
if(dashArray!=null)
setAttribute(SVGAttributes.SVG_STROKE_DASHARRAY, dashArray);
}
/**
* Sets the line join of the stroke of the SVG shape.
* @param svgLineJoin The line join to set. Must be SVG_LINEJOIN_VALUE_BEVEL or SVG_LINEJOIN_VALUE_MITER
* or SVG_LINEJOIN_VALUE_ROUND.
* @since 0.2
*/
public void setStrokeLineJoin(final String svgLineJoin) {
if(SVGAttributes.SVG_LINEJOIN_VALUE_BEVEL.equals(svgLineJoin) || SVGAttributes.SVG_LINEJOIN_VALUE_MITER.equals(svgLineJoin) ||
SVGAttributes.SVG_LINEJOIN_VALUE_ROUND.equals(svgLineJoin))
setAttribute(getUsablePrefix()+SVGAttributes.SVG_STROKE_LINEJOIN, svgLineJoin);
}
/**
* @return The stroke width of the element (if it is possible) or 1.
* @since 0.1
*/
public double getStrokeWidth() {
final String swStr = getSVGAttribute(SVGAttributes.SVG_STROKE_WIDTH, getUsablePrefix());
double sw;
try {
sw = swStr==null ? parent==null ? 1 : parent.getStrokeWidth() :
new SVGLengthParser(swStr).parseLength().getValue();
} catch(final ParseException e){ sw = 1; }
return sw;
}
/**
* @return The dash array of the element (if it is possible) or null.
* @since 0.1
*/
public String getStrokeDasharray() {
final String da = getSVGAttribute(SVGAttributes.SVG_STROKE_DASHARRAY, getUsablePrefix());
return da==null ? parent==null ? SVGAttributes.SVG_VALUE_NONE : parent.getStrokeDasharray() : da;
}
/**
* @return The line join of the element or its default value.
* @since 3.0
*/
public String getStrokeLinejoin() {
final String lj = getSVGAttribute(SVGAttributes.SVG_STROKE_LINEJOIN, getUsablePrefix());
return lj==null ? parent==null ? SVGAttributes.SVG_LINEJOIN_VALUE_MITER : parent.getStrokeLinejoin() : lj;
}
/**
* @return The line cap of the element or its default value.
* @since 0.1
*/
public String getStrokeLinecap() {
final String linecap = getSVGAttribute(SVGAttributes.SVG_STROKE_LINECAP, getUsablePrefix());
return linecap==null ? parent==null ? SVGAttributes.SVG_LINECAP_VALUE_BUTT : parent.getStrokeLinecap() : linecap;
}
/**
* @return The miter limit of the element or its default value.
* @since 0.1
*/
public double getStrokeMiterlimit() {
final String linecap = getSVGAttribute(SVGAttributes.SVG_STROKE_MITERLIMIT, getUsablePrefix());
double lc;
if(linecap!=null)
try { lc = Double.parseDouble(linecap); }
catch(final Exception e) { lc = 4.; }
else
lc = parent==null ? 4. : parent.getStrokeMiterlimit();
return lc;
}
/**
* @return The font-size value in point of the element, or from one of its parents.
*/
public float getFontSize() {
final String fs = getSVGAttribute(SVGAttributes.SVG_FONT_SIZE, getUsablePrefix());
return fs==null ? parent==null ? SVGLengthParser.FontSize.MEDIUM.getPointValue() :
parent.getFontSize() : SVGLengthParser.fontSizetoPoint(fs);
}
/**
* @return The defined or inherited font family. Otherwise, an empty string.
*/
public String getFontFamily() {
final String fam = getSVGAttribute(SVGAttributes.SVG_FONT_FAMILY, getUsablePrefix());
return fam==null ? parent==null ? "" : parent.getFontFamily() : fam; //$NON-NLS-1$
}
/**
* @return The defined or inherited font style. Otherwise, the default value "normal" is returned.
*/
public String getFontStyle() {
final String style = getSVGAttribute(SVGAttributes.SVG_FONT_STYLE, getUsablePrefix());
return style==null ? parent==null ? SVGAttributes.SVG_FONT_STYLE_NORMAL : parent.getFontStyle() : style;
}
/**
* @return The defined or inherited font weight. Otherwise, the default value "normal" is returned.
*/
public String getFontWeight() {
final String weight = getSVGAttribute(SVGAttributes.SVG_FONT_WEIGHT, getUsablePrefix());
return weight==null ? parent==null ? SVGAttributes.SVG_FONT_WEIGHT_NORMAL : parent.getFontWeight() : weight;
}
/**
* Sets the colour of the filling.
* @param c The new filling colour.
* @since 0.1
*/
public void setFill(final Color c) {
if(c!=null)
setAttribute(getUsablePrefix()+SVGAttributes.SVG_FILL, CSSColors.INSTANCE.getColorName(c, true));
}
/**
* @return The fill content of the element or its default value.
* @since 0.1
*/
public String getFill() {
final String fill = getSVGAttribute(SVGAttributes.SVG_FILL, getUsablePrefix());
return fill==null ? parent==null ? CSSColors.CSS_BLACK_NAME : parent.getFill() : fill;
}
/**
* Sets The colour of the stroke.
* @param c The new colour of the stroke (must not be null).
* @since 0.1
*/
public void setStroke(final Color c) {
if(c!=null)
setAttribute(getUsablePrefix()+SVGAttributes.SVG_STROKE, CSSColors.INSTANCE.getColorName(c, true));
}
/**
* @return The fill content of the element (if it is possible) or null.
* @since 0.1
*/
public Color getStroke() {
final String stroke = getSVGAttribute(SVGAttributes.SVG_STROKE, getUsablePrefix());
return stroke==null ? parent==null ? null : parent.getStroke() : CSSColors.INSTANCE.getRGBColour(stroke);
}
/**
* @param uri The URI that will be used parsed to extract a prefix.
* @return The prefix followed by ':' if there is a prefix. An empty string is returned in the other case.
* @since 0.1
*/
public String getUsablePrefix(final String uri) {
final String pref = lookupPrefix(uri);
return pref==null || pref.isEmpty() ? "" : pref+':';//$NON-NLS-1$
}
/**
* @return The list of transformations of the current SVG element (may be null).
* @since 0.1
*/
public SVGTransformList getTransform() {
return transform;
}
/**
* @return The list of all the transformations of the node's parents followed by the node's transformations.
* The first transformations will be the transformations of the oldest parent and the last ones, the
* transformations of the current node. If no transformation are defined, an empty list is returned.
* @since 0.1
*/
public SVGTransformList getWholeTransform() {
final SVGTransformList tl = new SVGTransformList(); // The list that will be returned.
SVGElement p = getParent(); // A parent element.
if(getTransform()!=null)
tl.addAll(getTransform());
while(p!=null) {
if(p.getTransform()!=null)
tl.addAll(0, p.getTransform());
p = p.getParent();
}
return tl;
}
/**
* Sets the owner document of the node.
* @param doc The document to set.
* @since 2.0.0
*/
public void setOwnerDocument(final SVGDocument doc) {
if(doc!=null) {
ownerDocument = doc;
for(int i=0, size=children.getLength(); i<size; i++)
children.item(i).setOwnerDocument(doc);
}
}
/**
* @param deep If deep equals to 1, only direct children will be considered, and so on.
* @return The number of children of the node.
*/
public int getNbChildren(final int deep) {
if(deep<1)
return 0;
int cpt = children.getLength();
for(final SVGElement e : children.getNodes())
cpt += e.getNbChildren(deep-1);
return cpt;
}
/**
* @return the stylesCSS
* @since 0.1
*/
public CSSStyleList getStylesCSS() {
return stylesCSS;
}
/**
* An SVG attribute can be defined in: its corresponding attribute (e.g. fill="...");
* the attribute style (e.g. style="fill:...;..."); a CSS stylesheet. This function
* returns the value of an SVG attribute by testing: 1. Its corresponding attribute;
* 2. The attribute style is 1. fails.
* @param attrName The name of the researched attribute.
* @param prefix The usable prefix.
* @return The found value or null.
* @since 2.0.6
*/
public String getSVGAttribute(final String attrName, final String prefix) {
if(attrName==null) return null;
String value = getAttribute((prefix==null?"":prefix) + attrName); //$NON-NLS-1$
if(value==null && stylesCSS!=null)
value = stylesCSS.get(attrName);
return value;
}
}