org.sonar.plugins.xml.highlighting.XMLHighlighting.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.xml.highlighting.XMLHighlighting.java

Source

/*
 * SonarQube XML Plugin
 * Copyright (C) 2010 SonarSource
 * sonarqube@googlegroups.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sonar.plugins.xml.highlighting;

import com.google.common.io.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.plugins.xml.checks.XmlFile;

import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public class XMLHighlighting {

    private static final String XML_DECLARATION_TAG = "<?xml";
    private final int delta;

    private List<HighlightingData> highlighting = new ArrayList<>();
    private String content;

    private int currentStartOffset = -1;
    private String currentCode = null;

    private static final Logger LOG = LoggerFactory.getLogger(XMLHighlighting.class);

    public XMLHighlighting(XmlFile xmlFile, Charset charset) throws IOException {
        content = Files.toString(xmlFile.getIOFile(), charset);
        delta = xmlFile.getOffsetDelta();

        try {
            highlightXML(new InputStreamReader(new FileInputStream(xmlFile.getIOFile()), charset));
        } catch (XMLStreamException e) {
            LOG.warn("Can't highlight following file : " + xmlFile.getIOFile().getAbsolutePath(), e);
        }
    }

    public XMLHighlighting(String xmlStrContent) {
        delta = 0;
        content = xmlStrContent;
        try {
            highlightXML(new StringReader(xmlStrContent));
        } catch (XMLStreamException e) {
            LOG.warn("Can't highlight following code : \n" + xmlStrContent, e);
        }
    }

    public List<HighlightingData> getHighlightingData() {
        return highlighting;
    }

    public void highlightXML(Reader reader) throws XMLStreamException {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        factory.setProperty(XMLInputFactory.SUPPORT_DTD, "false");
        factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, "false");
        XMLStreamReader xmlReader = factory.createXMLStreamReader(reader);
        highlightXmlDeclaration();

        while (xmlReader.hasNext()) {
            Location prevLocation = xmlReader.getLocation();
            xmlReader.next();
            int startOffset = xmlReader.getLocation().getCharacterOffset();
            closeHighlighting(startOffset);

            switch (xmlReader.getEventType()) {
            case XMLStreamConstants.START_ELEMENT:
                highlightStartElement(xmlReader, startOffset);
                break;

            case XMLStreamConstants.END_ELEMENT:
                highlightEndElement(xmlReader, prevLocation, startOffset);
                break;

            case XMLStreamConstants.CDATA:
                highlightCData(startOffset);
                break;

            case XMLStreamConstants.DTD:
                highlightDTD(startOffset);
                break;

            case XMLStreamConstants.COMMENT:
                addUnclosedHighlighting(startOffset, "j");
                break;

            default:
                break;
            }
        }
    }

    private void highlightDTD(int startOffset) {
        int closingBracketStartOffset;
        closingBracketStartOffset = getTagClosingBracketStartOffset(startOffset);
        addHighlighting(startOffset, startOffset + 9, "j");
        addHighlighting(closingBracketStartOffset, closingBracketStartOffset + 1, "j");
    }

    private void highlightCData(int startOffset) {
        int closingBracketStartOffset = getCDATAClosingBracketStartOffset(startOffset);

        // 9 is length of "<![CDATA["
        addHighlighting(startOffset, startOffset + 9, "k");

        // highlight "]]>"
        addHighlighting(closingBracketStartOffset - 2, closingBracketStartOffset + 1, "k");
    }

    private void highlightEndElement(XMLStreamReader xmlReader, Location prevLocation, int startOffset) {
        int closingBracketStartOffset = getTagClosingBracketStartOffset(startOffset);

        boolean isEmptyElement = prevLocation.getLineNumber() == xmlReader.getLocation().getLineNumber()
                && prevLocation.getColumnNumber() == xmlReader.getLocation().getColumnNumber();

        if (isEmptyElement) {
            // empty (or autoclosing) element is raised twice as start and end element, so we need to highlight closing "/" which is placed just before ">"
            addHighlighting(closingBracketStartOffset - 1, closingBracketStartOffset, "k");

        } else {
            addHighlighting(startOffset, closingBracketStartOffset + 1, "k");
        }
    }

    private void highlightStartElement(XMLStreamReader xmlReader, int startOffset) {
        int closingBracketStartOffset = getTagClosingBracketStartOffset(startOffset);
        int endOffset = startOffset + getNameWithNamespaceLength(xmlReader) + 1;

        addHighlighting(startOffset, endOffset, "k");
        highlightAttributes(endOffset, closingBracketStartOffset);
        addHighlighting(closingBracketStartOffset, closingBracketStartOffset + 1, "k");
    }

    private void highlightXmlDeclaration() {
        int startOffset = content.startsWith(XmlFile.BOM_CHAR) ? 1 : 0;
        if (content.startsWith(XML_DECLARATION_TAG, startOffset)) {
            int closingBracketStartOffset = getTagClosingBracketStartOffset(startOffset);

            addHighlighting(startOffset, startOffset + XML_DECLARATION_TAG.length(), "k");
            highlightAttributes(startOffset + XML_DECLARATION_TAG.length(), closingBracketStartOffset);
            addHighlighting(closingBracketStartOffset - 1, closingBracketStartOffset + 1, "k");
        }
    }

    private void highlightAttributes(int from, int to) {
        int counter = from + 1;

        Integer startOffset = null;
        Character attributeValueQuote = null;

        while (counter < to) {
            char c = content.charAt(counter);

            if (startOffset == null && !Character.isWhitespace(c)) {
                startOffset = counter;
            }

            if (attributeValueQuote != null && attributeValueQuote == c) {
                addHighlighting(startOffset, counter + 1, "s");
                counter++;
                startOffset = null;
                attributeValueQuote = null;
            }

            if (c == '=' && attributeValueQuote == null) {
                addHighlighting(startOffset, counter, "c");

                do {
                    counter++;
                    c = content.charAt(counter);
                } while (c != '\'' && c != '"');

                startOffset = counter;
                attributeValueQuote = c;
            }

            counter++;
        }
    }

    private int getTagClosingBracketStartOffset(int startOffset) {
        return getClosingBracketStartOffset(startOffset, false);
    }

    private int getCDATAClosingBracketStartOffset(int startOffset) {
        return getClosingBracketStartOffset(startOffset, true);
    }

    private int getClosingBracketStartOffset(int startOffset, boolean isCDATA) {
        int counter = startOffset + 1;
        while (startOffset < content.length()) {
            if (content.charAt(counter) == '>' && bracketsBefore(isCDATA, counter)) {
                return counter;
            }
            counter++;
        }

        throw new IllegalStateException("No \">\" found.");
    }

    private boolean bracketsBefore(boolean isCDATA, int counter) {
        return !isCDATA || (content.charAt(counter - 1) == ']' && content.charAt(counter - 2) == ']');
    }

    private static int getNameWithNamespaceLength(XMLStreamReader streamReader) {
        int prefixLength = 0;
        if (!streamReader.getName().getPrefix().isEmpty()) {
            prefixLength = streamReader.getName().getPrefix().length() + 1;
        }

        return prefixLength + streamReader.getLocalName().length();
    }

    private void addHighlighting(int startOffset, int endOffset, String code) {
        highlighting.add(new HighlightingData(startOffset + delta, endOffset + delta, code));
    }

    private void addUnclosedHighlighting(int startOffset, String code) {
        currentStartOffset = startOffset;
        currentCode = code;
    }

    private void closeHighlighting(int endOffset) {
        if (currentCode != null) {
            addHighlighting(currentStartOffset, endOffset, currentCode);
            currentCode = null;
        }
    }

}