/* * Copyright 2011 Luke Usherwood. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.bettyluke.tracinstant.data; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** Parses the XML export from Trac, in particular to populate the HTML description. */ public class TracXmlTicketParser { private static final Set<String> TICKET_FIELDS = new TreeSet<>( Arrays.asList(new String[] { "title", "description"})); private static final Pattern TICKET_URL_NUMBER_FINDER = Pattern.compile(".*\\/(([0-9])+)\\/?$"); /** Never constructed */ private TracXmlTicketParser() {} public static TracXmlResult parse(InputSource src) throws IOException, SAXException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document dom = builder.parse(src); return readRSS(dom); } catch (DOMException | FactoryConfigurationError | ParserConfigurationException exc) { exc.printStackTrace(); } throw new IOException("DOM error"); } private static TracXmlResult readRSS(Document dom) throws IOException { Element rss = dom.getDocumentElement(); String rssTagName = rss.getTagName(); if (!"rss".equals(rssTagName)) { throw new IOException( "Document does not appear to be an RSS document. " + "Expected the XML root to be " + "'rss', but found: " + rssTagName); } for (Element child : DOMUtils.iterateChildElements(rss)) { if (child.getTagName() == "channel") { return readChannel(child); } } throw new IOException("Expected a single child of 'rss' node named 'channel'."); } private static TracXmlResult readChannel(Element channel) throws IOException { TracXmlResult data = new TracXmlResult(); for (Element child : DOMUtils.iterateChildElements(channel)) { if ("item".equals(child.getTagName())) { Ticket ticket = readItem(child); if (ticket != null) { data.addTicket(ticket); } } else if ("title".equals(child.getTagName())) { data.setTitle(child.getTextContent()); } else if ("link".equals(child.getTagName())) { data.setLink(child.getTextContent()); } else if ("description".equals(child.getTagName())) { data.setDescription(child.getTextContent()); } } return data; } private static Ticket readItem(Element item) throws IOException { int number = extractTicketNumber(item); Ticket ticket = new Ticket(number); for (Element child : DOMUtils.iterateChildElements(item)) { String tag = child.getTagName(); if (TICKET_FIELDS.contains(tag)) { // For now, we just set the Ticket's field name to the XML tag ticket.putField(tag, child.getTextContent()); } } return ticket; } private static int extractTicketNumber(Element item) throws IOException { // Could equally use the Title. Element linkElement = DOMUtils.findFirstChildElementNamed(item, "link"); if (linkElement == null) { throw new IOException("Invalid Ticket in RSS - no link found"); } String link = linkElement.getTextContent(); Matcher m = TICKET_URL_NUMBER_FINDER.matcher(link); if (m.matches()) { try { return Integer.parseInt(m.group(1)); } catch (NumberFormatException ex) { } } throw new IOException("Link doesn't look like a Trac ticket: " + link); } }