/** * Copyright (C) 2014-2017 Philip Helger (www.helger.com) * philip[at]helger[dot]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 com.helger.css.supplementary.parser; import java.io.Closeable; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import com.helger.commons.collection.impl.RingBufferLifo; import com.helger.commons.io.stream.NonBlockingPushbackReader; import com.helger.commons.string.ToStringGenerator; /** * A special CSS Codepoint reader that converts chars to Codepoints and keeps * track of the line and column number. * * @author Philip Helger */ public class CSSCodepointReader implements Closeable { private static final class Pos { private int m_nLine; private int m_nCol; public Pos () { this (1, 1); } public Pos (@Nonnegative final int nLine, @Nonnegative final int nCol) { m_nLine = nLine; m_nCol = nCol; } // Count line/column - \r and \f is handled in the input stream public void incPos (final int nCP) { if (nCP == '\n') { ++m_nLine; m_nCol = 1; } else ++m_nCol; } @Nonnull public Pos getClone () { return new Pos (m_nLine, m_nCol); } @Override public String toString () { return new ToStringGenerator (null).append ("Line", m_nLine).append ("Column", m_nCol).getToString (); } } private static final int PUSHBACK_COUNT = 10; private final NonBlockingPushbackReader m_aReader; // Status vars private Pos m_aPos = new Pos (); private final RingBufferLifo <Pos> m_aPosRB = new RingBufferLifo<> (PUSHBACK_COUNT, true); private Pos m_aTokenStartPos; private final StringBuilder m_aTokenImage = new StringBuilder (1024); public CSSCodepointReader (@Nonnull final CSSInputStream aCSSIS, @Nonnull final Charset aCharset) { // Pushback maybe 2 chars each m_aReader = new NonBlockingPushbackReader (new InputStreamReader (aCSSIS, aCharset), PUSHBACK_COUNT * 2); } @Nonnull public CSSCodepoint read () throws IOException { final int nHigh = m_aReader.read (); if (nHigh == -1) { // No increment in line/column number return CSSCodepoint.createEOF (); } CSSCodepoint ret; if (Character.isHighSurrogate ((char) nHigh)) { final char cLow = (char) m_aReader.read (); if (!Character.isLowSurrogate (cLow)) throw new IOException ("Malformed Codepoint sequence - invalid low surrogate"); ret = new CSSCodepoint ((char) nHigh, cLow); m_aTokenImage.append ((char) nHigh); m_aTokenImage.append (cLow); } else { ret = new CSSCodepoint (nHigh); m_aTokenImage.append ((char) nHigh); } // Create a copy before incrementing m_aPosRB.put (m_aPos.getClone ()); m_aPos.incPos (nHigh); return ret; } public void unread (@Nonnull final CSSCodepoint aCP) throws IOException { final int nValue = aCP.getValue (); if (aCP.isSingleChar ()) { // Quick one char version m_aReader.unread (nValue); m_aTokenImage.setLength (m_aTokenImage.length () - 1); } else { // Two char version (reverse order because of undo!) m_aReader.unread (Character.lowSurrogate (nValue)); m_aReader.unread (Character.highSurrogate (nValue)); m_aTokenImage.setLength (m_aTokenImage.length () - 2); } // Restore the previous position m_aPos = m_aPosRB.take (); } @Nonnull public CSSCodepoint peek () throws IOException { final int nHigh = m_aReader.read (); CSSCodepoint ret; if (Character.isHighSurrogate ((char) nHigh)) { final char cLow = (char) m_aReader.read (); if (!Character.isLowSurrogate (cLow)) throw new IOException ("Malformed Codepoint sequence - invalid low surrogate"); ret = new CSSCodepoint ((char) nHigh, cLow); m_aReader.unread (cLow); m_aReader.unread (nHigh); } else { if (nHigh == -1) ret = CSSCodepoint.createEOF (); else ret = new CSSCodepoint (nHigh); m_aReader.unread (nHigh); } return ret; } public void close () throws IOException { m_aReader.close (); } @Nonnegative public int getLineNumber () { return m_aPos.m_nLine; } @Nonnegative public int getColumnNumber () { return m_aPos.m_nCol; } @Nonnull public CSSCodepoint startToken () throws IOException { // Reset token related stuff m_aTokenStartPos = m_aPos.getClone (); m_aTokenImage.setLength (0); final CSSCodepoint ret = read (); return ret; } @Nonnegative public int getTokenStartLineNumber () { return m_aTokenStartPos.m_nLine; } @Nonnegative public int getTokenStartColumnNumber () { return m_aTokenStartPos.m_nCol; } @Nonnull public String getTokenImage () { return m_aTokenImage.toString (); } @Nonnull public CSSToken createToken (@Nonnull final ECSSTokenType eTokenType) { return new CSSToken (getTokenStartLineNumber (), getTokenStartColumnNumber (), getLineNumber (), getColumnNumber (), getTokenImage (), eTokenType); } }