OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 83 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.openoffice.spreadsheet;

import org.openconcerto.utils.CompareUtils;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A cell range.
 * 
 * @author Sylvain
 */
public final class Range {

    // added illegal characters to unquoted form from LibreOffice UI, especially ':' to avoid
    // mistakenly using it in the table name instead of as a separator
    private static final String tableNamePattern = "\\$?([^\\Q. '[]*?:/\\\\E]+|'([^']|'')+')";
    // added parens to capture cell addresses
    // \1 is sheet name, \4 cell address, \6 second sheet name, \9 second cell address
    private static final Pattern cellRangePattern = java.util.regex.Pattern.compile("^(" + tableNamePattern + ")?\\.(\\$?[A-Z]+\\$?[0-9]+)(:(" + tableNamePattern + ")?\\.(\\$?[A-Z]+\\$?[0-9]+))?");
    private static final Pattern LIST_SEPARATOR_PATTERN = Pattern.compile("^\\s+");

    /**
     * Parse a range.
     * 
     * @param range the string form, e.g. "Sheet1.A23:.AA34".
     * @return the parsed range.
     */
    static public final Range parse(String range) {
        final Matcher m = cellRangePattern.matcher(range);
        if (!m.matches())
            throw new IllegalStateException(range + " is not a valid range address");
        return parse(m);
    }

    static private final Range parse(final Matcher m) {
        final String sheet1 = SpreadSheet.parseSheetName(m.group(1));
        final String sheet2 = SpreadSheet.parseSheetName(m.group(6));

        final Point start = Table.resolve(m.group(4));
        final String cell2 = m.group(9);
        final Point end = cell2 == null ? null : Table.resolve(cell2);

        return new Range(sheet1, start, sheet2, end);
    }

    // 9.2.5 Cell Range Address List, used by "table:print-ranges" for example.
    // e.g. "Feuille1.A1:Feuille1.L65 Feuille1.A67:Feuille1.L107"
    static public final List<Range> parseList(String ranges) {
        if (ranges.isEmpty())
            return Collections.emptyList();

        final Matcher m = cellRangePattern.matcher(ranges);
        m.useAnchoringBounds(true);
        if (!m.find())
            throw new IllegalStateException(ranges + " is not a valid range address list");
        final Range first = parse(m);
        final int length = ranges.length();
        if (length == m.end())
            return Collections.singletonList(first);

        final List<Range> res = new ArrayList<>();
        res.add(first);
        final Matcher sepMatcher = LIST_SEPARATOR_PATTERN.matcher(ranges);
        sepMatcher.useAnchoringBounds(true);
        while (m.end() != length) {
            sepMatcher.region(m.end(), length);
            if (!sepMatcher.find())
                throw new IllegalStateException("Couldn't find separator at " + sepMatcher.regionStart() + " in " + ranges);
            m.region(sepMatcher.end(), length);
            if (!m.find())
                throw new IllegalStateException("Couldn't find range address at " + m.regionStart() + " in " + ranges);
            res.add(parse(m));
        }
        return res;
    }

    private final String sheet1, sheet2;
    private final Point start, end;

    /**
     * Create a new instance with a single cell.
     * 
     * @param sheet name of the sheet.
     * @param point coordinate of the cell.
     */
    public Range(String sheet, Point point) {
        this(sheet, point, point);
    }

    public Range(String sheet, Point startPoint, Point endPoint) {
        this(sheet, startPoint, null, endPoint);
    }

    /**
     * Create a new instance.
     * 
     * @param startSheet name of the start sheet.
     * @param startPoint coordinate of the start.
     * @param endSheet name of the end sheet, can be <code>null</code> if the range doesn't span
     *        multiple sheets.
     * @param endPoint coordinate of the end, can be <code>null</code> for single cell range.
     */
    public Range(String startSheet, Point startPoint, String endSheet, Point endPoint) {
        super();
        if (startSheet == null && endSheet != null)
            throw new NullPointerException("null start sheet, but non null endSheet : " + endSheet);
        this.sheet1 = startSheet;
        this.sheet2 = endSheet == null ? startSheet : endSheet;
        if (startPoint == null)
            throw new NullPointerException("Null start point");
        this.start = startPoint;
        if (endPoint == null && spanSheets())
            throw new IllegalArgumentException("End cell must be passed for range spanning sheets : " + getStartSheet() + " to " + getEndSheet());
        this.end = endPoint == null ? this.start : endPoint;
        assert this.start != null && this.end != null;
    }

    public final String getStartSheet() {
        return this.sheet1;
    }

    public final Point getStartPoint() {
        return this.start;
    }

    public final String getEndSheet() {
        return this.sheet2;
    }

    public final Point getEndPoint() {
        return this.end;
    }

    public final boolean spanSheets() {
        return !CompareUtils.equals(this.getStartSheet(), this.getEndSheet());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Range o = (Range) obj;
        return CompareUtils.equals(this.sheet1, o.sheet1) && this.start.equals(o.start) && CompareUtils.equals(this.sheet2, o.sheet2) && this.end.equals(o.end);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.sheet1 == null) ? 0 : this.sheet1.hashCode());
        result = prime * result + ((this.start == null) ? 0 : this.start.hashCode());
        result = prime * result + ((this.sheet2 == null) ? 0 : this.sheet2.hashCode());
        result = prime * result + ((this.end == null) ? 0 : this.end.hashCode());
        return result;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder(32);
        if (this.getStartSheet() != null)
            sb.append(this.getStartSheet());
        // the RelaxNG pattern requires the dot
        sb.append(".");
        sb.append(Table.getAddress(getStartPoint()));
        if (this.spanSheets()) {
            sb.append(":");
            sb.append(this.getEndSheet());
            sb.append(".");
            sb.append(Table.getAddress(getEndPoint()));
        } else if (!getEndPoint().equals(getStartPoint())) {
            sb.append(":");
            // the RelaxNG pattern requires the dot
            sb.append(".");
            sb.append(Table.getAddress(getEndPoint()));
        }
        return sb.toString();
    }
}