1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.history; 25 26 import java.io.BufferedInputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 import javax.xml.XMLConstants; 32 import javax.xml.parsers.ParserConfigurationException; 33 import javax.xml.parsers.SAXParser; 34 import javax.xml.parsers.SAXParserFactory; 35 import org.opengrok.indexer.logger.LoggerFactory; 36 import org.opengrok.indexer.util.Executor; 37 import org.xml.sax.Attributes; 38 import org.xml.sax.SAXException; 39 import org.xml.sax.ext.DefaultHandler2; 40 41 /** 42 * handles parsing the output of the {@code svn annotate} 43 * command into an annotation object. 44 */ 45 public class SubversionAnnotationParser implements Executor.StreamHandler { 46 private static final Logger LOGGER = LoggerFactory.getLogger(SubversionAnnotationParser.class); 47 48 /** 49 * Store annotation created by processStream. 50 */ 51 private final Annotation annotation; 52 53 private final String fileName; 54 55 /** 56 * @param fileName the name of the file being annotated 57 */ SubversionAnnotationParser(String fileName)58 public SubversionAnnotationParser(String fileName) { 59 annotation = new Annotation(fileName); 60 this.fileName = fileName; 61 } 62 63 /** 64 * Returns the annotation that has been created. 65 * 66 * @return annotation an annotation object 67 */ getAnnotation()68 public Annotation getAnnotation() { 69 return annotation; 70 } 71 72 @Override processStream(InputStream input)73 public void processStream(InputStream input) throws IOException { 74 SAXParserFactory factory = SAXParserFactory.newInstance(); 75 SAXParser saxParser; 76 try { 77 saxParser = factory.newSAXParser(); 78 saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant 79 saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // compliant 80 } catch (ParserConfigurationException | SAXException ex) { 81 IOException err = new IOException("Failed to create SAX parser", ex); 82 throw err; 83 } 84 85 AnnotateHandler handler = new AnnotateHandler(fileName, annotation); 86 try (BufferedInputStream in 87 = new BufferedInputStream(input)) { 88 saxParser.parse(in, handler); 89 } catch (Exception e) { 90 LOGGER.log(Level.SEVERE, 91 "An error occurred while parsing the xml output", e); 92 } 93 } 94 95 private static class AnnotateHandler extends DefaultHandler2 { 96 97 String rev; 98 String author; 99 final Annotation annotation; 100 final StringBuilder sb; 101 AnnotateHandler(String filename, Annotation annotation)102 AnnotateHandler(String filename, Annotation annotation) { 103 this.annotation = annotation; 104 sb = new StringBuilder(); 105 } 106 107 @Override startElement(String uri, String localName, String qname, Attributes attr)108 public void startElement(String uri, String localName, String qname, 109 Attributes attr) { 110 sb.setLength(0); 111 if (null != qname) { 112 switch (qname) { 113 case "entry": 114 rev = null; 115 author = null; 116 break; 117 case "commit": 118 rev = attr.getValue("revision"); 119 break; 120 } 121 } 122 } 123 124 @Override endElement(String uri, String localName, String qname)125 public void endElement(String uri, String localName, String qname) { 126 if (null != qname) { 127 switch (qname) { 128 case "author": 129 author = sb.toString(); 130 break; 131 case "entry": 132 annotation.addLine(rev, author, true); 133 break; 134 } 135 } 136 } 137 138 @Override characters(char[] arg0, int arg1, int arg2)139 public void characters(char[] arg0, int arg1, int arg2) { 140 sb.append(arg0, arg1, arg2); 141 } 142 } 143 } 144