001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2010 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.utils;
021
022import java.io.BufferedInputStream;
023import java.io.File;
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027import java.util.regex.Pattern;
028import javax.xml.parsers.DocumentBuilder;
029import javax.xml.parsers.DocumentBuilderFactory;
030import javax.xml.parsers.ParserConfigurationException;
031import javax.xml.xpath.XPath;
032import javax.xml.xpath.XPathFactory;
033import org.jetbrains.annotations.NotNull;
034import org.jetbrains.annotations.Nullable;
035import org.xml.sax.EntityResolver;
036import org.xml.sax.InputSource;
037
038/**
039 * Little helper class for XML, holds a {@link DocumentBuilder} and an {@link
040 * XPath} that are setup for Gridarta.
041 * @author Andreas Kirschbaum
042 */
043public class XmlHelper {
044
045    /**
046     * The {@link Pattern} for matching the directory part of a system ID.
047     */
048    @NotNull
049    private static final Pattern PATTERN_DIRECTORY_PART = Pattern.compile(".*/");
050
051    /**
052     * DocumentBuilder.
053     */
054    private final DocumentBuilder documentBuilder;
055
056    /**
057     * The XPath for using XPath.
058     */
059    private final XPath xpath;
060
061    /**
062     * Initialize the XML engine.
063     * @throws ParserConfigurationException in case the xml parser couldn't be
064     * set up
065     */
066    public XmlHelper() throws ParserConfigurationException {
067        // TODO: Change hard coded document builder factory to user configurable settings
068        final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
069        dbf.setCoalescing(true);
070        // dbf.setExpandEntityReferences(true); // true is default on this
071        dbf.setIgnoringComments(true);
072        dbf.setIgnoringElementContentWhitespace(true);
073        dbf.setNamespaceAware(true);
074        dbf.setValidating(true);
075        dbf.setXIncludeAware(true);
076        documentBuilder = dbf.newDocumentBuilder();
077        documentBuilder.setEntityResolver(new GridartaEntityResolver());
078        xpath = XPathFactory.newInstance().newXPath();
079    }
080
081    /**
082     * Return the DocumentBuilder.
083     * @return the document builder
084     */
085    public DocumentBuilder getDocumentBuilder() {
086        return documentBuilder;
087    }
088
089    /**
090     * Return the XPath for using XPath.
091     * @return the XPath
092     */
093    public XPath getXPath() {
094        return xpath;
095    }
096
097    /**
098     * Implements an {@link EntityResolver} for looking up built-in .dtd files.
099     */
100    private static class GridartaEntityResolver implements EntityResolver {
101
102        @Nullable
103        @Override
104        public InputSource resolveEntity(final String publicId, final String systemId) throws IOException {
105            if (systemId.endsWith(".xml")) {
106                return null;
107            }
108
109            InputSource inputSource = null;
110            final URL url = IOUtils.getResource(new File("system/dtd"), PATTERN_DIRECTORY_PART.matcher(systemId).replaceAll(""));
111            final InputStream inputStream = url.openStream();
112            try {
113                final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
114                try {
115                    inputSource = new InputSource(bufferedInputStream);
116                } finally {
117                    if (inputSource == null) {
118                        bufferedInputStream.close();
119                    }
120                }
121            } finally {
122                if (inputSource == null) {
123                    inputStream.close();
124                }
125            }
126            return inputSource;
127        }
128    }
129
130}