/* * Copyright (c) 2008 Mozilla Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package nu.validator.svgresearch; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.DTDHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.ext.DeclHandler; import org.xml.sax.ext.LexicalHandler; public class SvgAnalysisHandler implements ContentHandler, LexicalHandler, DTDHandler, DeclHandler { private ScoreBoard generalScoreBoard = new ScoreBoard(); private Map<String, ScoreBoard> scoreBoardsByCreator = new HashMap<String, ScoreBoard>(); private boolean nonSvgRoot = false; private boolean nonNamespaceSvgRoot = false; private boolean otherNamespaceSvgRoot = false; private boolean hasFlowRoot = false; private final Set<NameTriple> prefixedSvgElements = new HashSet<NameTriple>(); private final Set<NameTriple> foreignElementsInMetadata = new HashSet<NameTriple>(); private final Set<NameTriple> foreignElementsElsewhere = new HashSet<NameTriple>(); private final Set<String> unconventionalXLinkPrefixes = new HashSet<String>(); private final Set<NameTriple> prefixedAttributes = new HashSet<NameTriple>(); private final Set<NameTriple> fontAttributes = new HashSet<NameTriple>(); private final Set<String> fontParent = new HashSet<String>(); private final Set<String> piTargets = new HashSet<String>(); private final Set<String> requiredExtensions = new HashSet<String>(); private final LinkedList<String> stack = new LinkedList<String>(); private final Set<String> internalEntities = new HashSet<String>(); private String creator = null; private boolean hasDoctype = false; private boolean hasInternalSubset = false; private boolean hasMetadata = false; private boolean hasStyleAttribute = false; private boolean hasPresentationAttributes = false; private boolean hasStyleElement = false; private boolean hasDefinitionElementsOutsideDefs = false; private void push(String name) { stack.addFirst(name); } private void pop() { stack.removeFirst(); } private String peek() { if (stack.size() == 0) { return null; } return stack.getFirst(); } private boolean hasAncestor(String name) { for (String node : stack) { if (node == name) { return true; } } return false; } @SuppressWarnings("boxing") private void fillScoreBoard(ScoreBoard board) { board.total++; if (nonSvgRoot) { board.nonSvgRoot++; } if (nonNamespaceSvgRoot) { board.nonNamespaceSvgRoot++; } if (otherNamespaceSvgRoot) { board.otherNamespaceSvgRoot++; } if (hasFlowRoot) { board.hasFlowRoot++; } if (hasDoctype) { board.hasDoctype++; } if (hasInternalSubset) { board.hasInternalSubset++; } if (hasMetadata) { board.hasMetadata++; } if (hasStyleAttribute) { board.hasStyleAttribute++; } if (hasPresentationAttributes) { board.hasPresentationAttributes++; } if (hasStyleElement) { board.hasStyleElement++; } if (hasDefinitionElementsOutsideDefs) { board.hasDefinitionElementsOutsideDefs++; } Integer creatorCount = board.creator.get(creator); if (creatorCount == null) { board.creator.put(creator, 1); } else { board.creator.put(creator, creatorCount.intValue() + 1); } fillMapFromSetTriple(board.prefixedSvgElements, prefixedSvgElements); fillMapFromSetTriple(board.foreignElementsInMetadata, foreignElementsInMetadata); fillMapFromSetTriple(board.foreignElementsElsewhere, foreignElementsElsewhere); fillMapFromSetTriple(board.prefixedAttributes, prefixedAttributes); fillMapFromSetTriple(board.fontAttributes, fontAttributes); fillMapFromSetString(board.unconventionalXLinkPrefixes, unconventionalXLinkPrefixes); fillMapFromSetString(board.fontParent, fontParent); fillMapFromSetString(board.piTargets, piTargets); fillMapFromSetString(board.requiredExtensions, requiredExtensions); fillMapFromSetString(board.internalEntities, internalEntities); } @SuppressWarnings("boxing") private void fillMapFromSetString(Map<String, Integer> map, Set<String> set) { if (set.size() > 0) { Integer count = map.get("ANY"); if (count == null) { map.put("ANY", 1); } else { map.put("ANY", count.intValue() + 1); } } for (String object : set) { Integer count = map.get(object); if (count == null) { map.put(object, 1); } else { map.put(object, count.intValue() + 1); } } } @SuppressWarnings("boxing") private void fillMapFromSetTriple(Map<NameTriple, Integer> map, Set<NameTriple> set) { if (set.size() > 0) { Integer count = map.get(NameTriple.ANY_MARKER); if (count == null) { map.put(NameTriple.ANY_MARKER, 1); } else { map.put(NameTriple.ANY_MARKER, count.intValue() + 1); } } for (NameTriple object : set) { Integer count = map.get(object); if (count == null) { map.put(object, 1); } else { map.put(object, count.intValue() + 1); } } } public void print() { generalScoreBoard.printScoreBoard(); for (Map.Entry<String, ScoreBoard> entry : scoreBoardsByCreator.entrySet()) { System.out.println("\n********************************\n"); System.out.println(entry.getKey()); entry.getValue().printScoreBoard(); } } public void startDocument() throws SAXException { nonSvgRoot = false; nonNamespaceSvgRoot = false; otherNamespaceSvgRoot = false; hasFlowRoot = false; prefixedSvgElements.clear(); foreignElementsInMetadata.clear(); foreignElementsElsewhere.clear(); unconventionalXLinkPrefixes.clear(); prefixedAttributes.clear(); piTargets.clear(); stack.clear(); creator = null; hasDoctype = false; fontAttributes.clear(); hasInternalSubset = false; hasMetadata = false; fontParent.clear(); internalEntities.clear(); hasStyleAttribute = false; hasStyleElement = false; hasDefinitionElementsOutsideDefs = false; hasPresentationAttributes = false; requiredExtensions.clear(); } public void endDocument() throws SAXException { if (creator == null) { creator = "NO CREATOR"; } fillScoreBoard(generalScoreBoard); ScoreBoard creatorBoard = scoreBoardsByCreator.get(creator); if (creatorBoard == null) { creatorBoard = new ScoreBoard(); scoreBoardsByCreator.put(creator, creatorBoard); } fillScoreBoard(creatorBoard); } public void endElement(String uri, String localName, String name) throws SAXException { pop(); } public void startElement(String uri, String localName, String qname, Attributes atts) throws SAXException { String parent = peek(); if (parent == null) { if (localName == "svg") { if (uri == "") { nonNamespaceSvgRoot = true; } else if (uri != "http://www.w3.org/2000/svg") { otherNamespaceSvgRoot = true; } } else { nonSvgRoot = true; } } if (localName == "flowRoot") { hasFlowRoot = true; } else if (!nonSvgRoot && "font" == localName) { fontParent.add(peek()); } else if ("style" == localName) { hasStyleElement = true; } if ("clipPath" == localName || "color-profile" == localName || "cursor" == localName || "filter" == localName || "font" == localName || "linearGradient" == localName || "marker" == localName || "mask" == localName || "pattern" == localName || "radialGradient" == localName || "solidColor" == localName || "symbol" == localName) { if (hasAncestor("defs")) { hasDefinitionElementsOutsideDefs = true; } } NameTriple eltTriple = new NameTriple(localName, qname, uri); if (uri == "http://www.w3.org/2000/svg") { if (eltTriple.getPrefix() != "") { // we've got a prefixed element in the SVG ns prefixedSvgElements.add(eltTriple); } } else { if (hasAncestor("metadata")) { hasMetadata = true; foreignElementsInMetadata.add(eltTriple); } else { foreignElementsElsewhere.add(eltTriple); } } int len = atts.getLength(); for (int i = 0; i < len; i++) { String attLocal = atts.getLocalName(i); String attQname = atts.getQName(i); String attUri = atts.getURI(i); NameTriple attTriple = new NameTriple(attLocal, attQname, attUri); if (!nonSvgRoot && "font" == localName) { fontAttributes.add(attTriple); } String prefix = attTriple.getPrefix(); if (prefix != "") { if ("http://www.w3.org/XML/1998/namespace" == attUri) { // do nothing } else if ("http://www.w3.org/1999/xlink" == attUri) { if (prefix != "xlink") { unconventionalXLinkPrefixes.add(prefix); } } else { prefixedAttributes.add(attTriple); } } else { if ("style" == attLocal) { hasStyleAttribute = true; } else if ("requiredExtensions" == attLocal) { requiredExtensions.add(atts.getValue(i)); } else if ("alignment-baseline" == attLocal || "baseline-shift" == attLocal || "clip" == attLocal || "clip-path" == attLocal || "clip-rule" == attLocal || "color" == attLocal || "color-interpolation" == attLocal || "color-interpolation-filters" == attLocal || "color-profile" == attLocal || "color-rendering" == attLocal || "cursor" == attLocal || "direction" == attLocal || "display" == attLocal || "dominant-baseline" == attLocal || "enable-background" == attLocal || "fill" == attLocal || "fill-opacity" == attLocal || "fill-rule" == attLocal || "filter" == attLocal || "flood-color" == attLocal || "flood-opacity" == attLocal || "font" == attLocal || "font-family" == attLocal || "font-size" == attLocal || "font-size-adjust" == attLocal || "font-stretch" == attLocal || "font-style" == attLocal || "font-variant" == attLocal || "font-weight" == attLocal || "glyph-orientation-horizontal" == attLocal || "glyph-orientation-vertical" == attLocal || "image-rendering" == attLocal || "kerning" == attLocal || "letter-spacing" == attLocal || "lighting-color" == attLocal || "marker" == attLocal || "marker-end" == attLocal || "marker-mid" == attLocal || "marker-start" == attLocal || "mask" == attLocal || "opacity" == attLocal || "overflow" == attLocal || "pointer-events" == attLocal || "shape-rendering" == attLocal || "stop-color" == attLocal || "stop-opacity" == attLocal || "stroke" == attLocal || "stroke-dasharray" == attLocal || "stroke-dashoffset" == attLocal || "stroke-linecap" == attLocal || "stroke-linejoin" == attLocal || "stroke-miterlimit" == attLocal || "stroke-opacity" == attLocal || "stroke-width" == attLocal || "text-anchor" == attLocal || "text-decoration" == attLocal || "text-rendering" == attLocal || "unicode-bidi" == attLocal || "visibility" == attLocal || "word-spacing" == attLocal || "writing-mode" == attLocal || "audio-level" == attLocal || "display-align" == attLocal || "line-increment" == attLocal || "solid-color" == attLocal || "solid-opacity" == attLocal || "text-align" == attLocal || "vector-effect" == attLocal || "viewport-fill" == attLocal || "viewport-fill-opacity" == attLocal) { hasPresentationAttributes = true; } } } if (uri == "http://www.w3.org/2000/svg") { push(localName); } else { push(""); } } public void comment(char[] ch, int start, int length) throws SAXException { if (creator == null) { String str = new String(ch, start, length); if (str.startsWith(" Created with Arkyan's SVGCensus script")) { creator = "Arkyan's SVGCensus script"; } else if (str.startsWith(" Created with ")) { int index = str.indexOf(' ', 14); creator = str.substring(14, index); } else if (str.startsWith(" Generator: Adobe Illustrator")) { creator = "Adobe Illustrator"; } else if (str.startsWith(" Generator: ")) { int index = str.indexOf(' ', 12); creator = str.substring(12, index); } } } /** * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ public void characters(char[] ch, int start, int length) throws SAXException { } public void endPrefixMapping(String prefix) throws SAXException { } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { } public void processingInstruction(String target, String data) throws SAXException { piTargets.add(target); } public void setDocumentLocator(Locator locator) { } public void skippedEntity(String name) throws SAXException { } public void startPrefixMapping(String prefix, String uri) throws SAXException { } public void endCDATA() throws SAXException { } public void endDTD() throws SAXException { } public void endEntity(String name) throws SAXException { } public void startCDATA() throws SAXException { } public void startDTD(String name, String publicId, String systemId) throws SAXException { hasDoctype = true; } public void startEntity(String name) throws SAXException { } public void notationDecl(String name, String publicId, String systemId) throws SAXException { hasInternalSubset = true; } public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { hasInternalSubset = true; } public void attributeDecl(String name, String name2, String type, String mode, String value) throws SAXException { hasInternalSubset = true; } public void elementDecl(String name, String model) throws SAXException { hasInternalSubset = true; } public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException { hasInternalSubset = true; } public void internalEntityDecl(String name, String value) throws SAXException { hasInternalSubset = true; internalEntities.add(name); } }