Use Pest-based parser for BLT files

Support comments, optional newlines, etc.
This commit is contained in:
RunasSudo 2021-07-23 16:45:54 +10:00
parent 3b8ccd097e
commit cca097f943
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
15 changed files with 309 additions and 106 deletions

136
Cargo.lock generated
View File

@ -47,13 +47,34 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
@ -74,6 +95,12 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -198,13 +225,22 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
"generic-array 0.14.4",
]
[[package]]
@ -219,6 +255,12 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "flate2"
version = "1.0.20"
@ -240,6 +282,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
@ -365,6 +416,12 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matrixmultiply"
version = "0.3.1"
@ -460,6 +517,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -485,6 +548,8 @@ dependencies = [
"num-rational",
"num-traits",
"paste",
"pest",
"pest_derive",
"predicates",
"rug",
"sha2",
@ -504,6 +569,49 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "predicates"
version = "1.0.8"
@ -651,17 +759,29 @@ version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"opaque-debug",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
@ -699,6 +819,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.7.1"

View File

@ -13,6 +13,8 @@ git-version = "0.3.4"
ibig = "0.3.2"
itertools = "0.10.1"
ndarray = "0.15.3"
pest = "2.1.3"
pest_derive = "2.1.0"
predicates = "1.0.8"
num-traits = "0.2"
sha2 = "0.9.5"

View File

@ -1,6 +1,6 @@
# BLT file format
OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/openstv), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file).
OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/help/overview#blt-file-format), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file).
The file format is as follows:
@ -21,14 +21,16 @@ The file format is as follows:
"Title"
```
The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies. This must be on its own line.
The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies.
The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`. This must, if present, be on its own line.
The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`.
The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences. Each such set of ballots must be on its own line.
The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences.
The end of the list of ballots must be indicated with a single `0`, which must be on its own line.
The end of the list of ballots must be indicated with a single `0`.
The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name must be surrounded by quotation marks, and must appear on its own line.
The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name, unless a single word, must be surrounded by quotation marks.
The final line gives the name of the election, which must appear on its own line.
The final line gives the name of the election, which, unless a single word, must be surrounded by quotation marks.
Newlines are optional, but if not provided, the `0` at the end of each ballot's preferences is mandatory.

View File

@ -18,8 +18,11 @@
use crate::constraints::{Constraints, ConstraintMatrix};
use crate::logger::Logger;
use crate::numbers::Number;
use crate::parser::blt;
use crate::sharandom::SHARandom;
use pest::Parser;
use std::collections::HashMap;
/// An election to be counted
@ -40,12 +43,12 @@ pub struct Election<N> {
impl<N: Number> Election<N> {
/// Parse the given BLT file and return an [Election]
pub fn from_blt<I: Iterator<Item=String>>(mut lines: I) -> Self {
// Read first line
let line = lines.next().expect("Unexpected EOF");
let mut bits = line.split(" ");
let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
pub fn from_blt(input: &str) -> Self {
let mut main = blt::BLTParser::parse(blt::Rule::main, input).expect("Syntax Error").next().unwrap().into_inner();
let mut header = main.next().unwrap().into_inner();
let num_candidates = header.next().unwrap().as_str().parse().unwrap();
let seats = header.next().unwrap().as_str().parse().unwrap();
// Initialise the Election object
let mut election = Election {
@ -57,57 +60,47 @@ impl<N: Number> Election<N> {
constraints: None,
};
// Read withdrawn candidates
let withdrawn_pairs = main.next().unwrap().into_inner();
for withdrawn_cand in withdrawn_pairs {
let val = withdrawn_cand.as_str();
let val: usize = val[1..val.len()].parse().unwrap();
election.withdrawn_candidates.push(val - 1);
}
// Read ballots
for line in &mut lines {
if line == "0" {
break;
}
let ballot_list = main.next().unwrap().into_inner();
for ballot in ballot_list {
let mut ballot_pairs = ballot.into_inner();
let mut bits = line.split(" ");
if line.starts_with("-") {
// Withdrawn candidates
for bit in bits.into_iter() {
let val = bit[1..bit.len()].parse::<usize>().expect("Syntax Error");
election.withdrawn_candidates.push(val - 1);
}
continue;
}
let value = N::parse(bits.next().expect("Syntax Error"));
let value = N::parse(ballot_pairs.next().unwrap().as_str());
let mut ballot = Ballot {
orig_value: value,
preferences: Vec::new(),
};
for preference in bits {
if preference != "0" {
let preference = preference.parse::<usize>().expect("Syntax Error");
ballot.preferences.push(preference - 1);
}
let ballot_preferences = ballot_pairs.next().unwrap().into_inner();
for preference in ballot_preferences {
let preference: usize = preference.as_str().parse().unwrap();
ballot.preferences.push(preference - 1);
}
election.ballots.push(ballot);
}
// Zero line
main.next();
// Read candidates
for line in lines.by_ref().take(num_candidates) {
let mut line = &line[..];
if line.starts_with("\"") && line.ends_with("\"") {
line = &line[1..line.len()-1];
}
election.candidates.push(Candidate { name: line.to_string() });
for _ in 0..num_candidates {
let string = main.next().expect("Expected candidate name");
election.candidates.push(Candidate { name: blt::unwrap_string(string).to_string() });
}
// Read name
let line = lines.next().expect("Syntax Error");
let mut line = &line[..];
if line.starts_with("\"") && line.ends_with("\"") {
line = &line[1..line.len()-1];
}
election.name.push_str(line);
let string = main.next().expect("Expected election title");
election.name.push_str(blt::unwrap_string(string));
return election;
}
@ -321,7 +314,7 @@ impl<'a, N: Number> CountCard<'a, N> {
pub struct Parcel<'a, N> {
/// [Vote]s in this parcel
pub votes: Vec<Vote<'a, N>>,
/// Order for sorting with [opentally::stv::ExclusionMethod::BySource]
/// Order for sorting with [crate::stv::ExclusionMethod::BySource]
pub source_order: usize,
}

View File

@ -19,6 +19,8 @@
//! Open source counting software for various preferential voting election systems
/// File parsers
pub mod parser;
/// Data types and logic for constraints on elections
pub mod constraints;
/// Data types for representing abstract elections
@ -36,6 +38,8 @@ pub mod ties;
use git_version::git_version;
use wasm_bindgen::prelude::wasm_bindgen;
#[macro_use]
extern crate pest_derive;
/// The git revision of this OpenTally build
pub const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown");

View File

@ -23,7 +23,7 @@ use opentally::stv;
use clap::{AppSettings, Clap};
use std::cmp::max;
use std::fs::File;
use std::fs::{self, File};
use std::io::{self, BufRead};
use std::ops;
@ -190,29 +190,28 @@ fn main() {
let Command::STV(cmd_opts) = opts.command;
// Read BLT file
let file = File::open(&cmd_opts.filename).expect("IO Error");
let lines = io::BufReader::new(file).lines();
let file = fs::read_to_string(&cmd_opts.filename).expect("IO Error");
// Create and count election according to --numbers
if cmd_opts.numbers == "rational" {
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let mut election: Election<Rational> = Election::from_blt(&file);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
// Must specify ::<N> here and in a few other places because ndarray causes E0275 otherwise
count_election::<Rational>(election, cmd_opts);
} else if cmd_opts.numbers == "float64" {
let mut election: Election<NativeFloat64> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let mut election: Election<NativeFloat64> = Election::from_blt(&file);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<NativeFloat64>(election, cmd_opts);
} else if cmd_opts.numbers == "fixed" {
Fixed::set_dps(cmd_opts.decimals);
let mut election: Election<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let mut election: Election<Fixed> = Election::from_blt(&file);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<Fixed>(election, cmd_opts);
} else if cmd_opts.numbers == "gfixed" {
GuardedFixed::set_dps(cmd_opts.decimals);
let mut election: Election<GuardedFixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let mut election: Election<GuardedFixed> = Election::from_blt(&file);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<GuardedFixed>(election, cmd_opts);
}

36
src/parser/blt.pest Normal file
View File

@ -0,0 +1,36 @@
main = {
SOI ~ NEWLINE*
~ header ~ NEWLINE*
~ withdrawn ~ NEWLINE*
~ ballot_list
~ zero ~ NEWLINE*
~ (string ~ NEWLINE*)+
~ EOI
}
header = {
integer ~ integer
}
withdrawn = {
(withdrawn_cand ~ NEWLINE*)*
}
withdrawn_cand = @{ "-" ~ integer }
ballot_list = { (ballot ~ NEWLINE*)* }
ballot = {
number ~ ballot_preferences ~ (zero ~ NEWLINE? | NEWLINE)
}
ballot_preferences = { integer* }
zero = { "0" }
integer = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
decimal = @{ "0." ~ ASCII_DIGIT+ | integer ~ "." ~ ASCII_DIGIT+ }
number = { decimal | integer }
quoted_string = @{ "\"" ~ (!"\"" ~ !NEWLINE ~ ANY)* ~ "\"" }
raw_string = @{ (!" " ~ !NEWLINE ~ ANY)+ }
string = { quoted_string | raw_string }
WHITESPACE = _{ " " }
COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* ~ (&NEWLINE | &EOI) }

44
src/parser/blt.rs Normal file
View File

@ -0,0 +1,44 @@
/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#![allow(missing_docs)]
use pest::iterators::Pair;
#[derive(Parser)]
#[grammar = "parser/blt.pest"]
pub struct BLTParser;
/// Convert the specified [Pair<Rule>] into a &str
pub fn unwrap_string(string: Pair<Rule>) -> &str {
match string.as_rule() {
Rule::string => {
let string = string.into_inner().next().unwrap();
match string.as_rule() {
Rule::raw_string => {
return string.as_str();
}
Rule::quoted_string => {
let string = string.as_str();
return &string[1..string.len()-1];
}
_ => unreachable!()
}
}
_ => panic!("Non-string passed to unwrap_string")
}
}

19
src/parser/mod.rs Normal file
View File

@ -0,0 +1,19 @@
/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// BLT file parser
pub mod blt;

View File

@ -56,7 +56,7 @@ macro_rules! impl_type {
// Install panic! hook
console_error_panic_hook::set_once();
let election: Election<$type> = Election::from_blt(text.lines().map(|s| s.to_string()).into_iter());
let election: Election<$type> = Election::from_blt(&text);
return [<Election$type>](election);
}

View File

@ -25,7 +25,7 @@ use csv::StringRecord;
use flate2::bufread::GzDecoder;
use std::fs::File;
use std::io::{self, BufRead};
use std::io::{self, Read};
#[test]
fn aec_tas19_rational() {
@ -45,12 +45,12 @@ fn aec_tas19_rational() {
// Decompress BLT
let file = File::open("tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz").expect("IO Error");
let file_reader = io::BufReader::new(file);
let gz_decoder = GzDecoder::new(file_reader);
let gz_reader = io::BufReader::new(gz_decoder);
let lines = gz_reader.lines();
let mut gz_decoder = GzDecoder::new(file_reader);
let mut input = String::new();
gz_decoder.read_to_string(&mut input).expect("IO Error");
// Read BLT
let election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let election: Election<Rational> = Election::from_blt(&input);
// Validate candidate names
for (i, candidate) in candidates.iter().enumerate() {

View File

@ -22,7 +22,7 @@ use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::Rational;
use opentally::stv;
use std::fs::File;
use std::fs::{self, File};
use std::io::{self, BufRead};
#[test]
@ -30,10 +30,8 @@ fn prsa1_constr1_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
// Read CON
let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error");
@ -94,10 +92,8 @@ fn prsa1_constr2_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
// Read CON
let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error");
@ -158,10 +154,8 @@ fn prsa1_constr3_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
// Read CON
let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error");

View File

@ -21,8 +21,7 @@ use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::{Fixed, NativeFloat64, Number};
use opentally::stv;
use std::io::{self, BufRead};
use std::fs::File;
use std::fs;
// Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation
#[test]
@ -86,11 +85,8 @@ fn meek06_ers97_fixed12() {
Fixed::set_dps(12);
// Read BLT
let file = File::open("tests/data/ers97.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let election: Election<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
let election: Election<Fixed> = Election::from_blt(&file);
// Initialise count state
let mut state = CountState::new(&election);
@ -162,11 +158,8 @@ fn meeknz_ers97_fixed12() {
Fixed::set_dps(12);
// Read BLT
let file = File::open("tests/data/ers97.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let election: Election<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
let election: Election<Fixed> = Election::from_blt(&file);
// Initialise count state
let mut state = CountState::new(&election);

View File

@ -15,16 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn
use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::{Fixed, GuardedFixed, Number};
use opentally::stv;
use xmltree::Element;
use std::io::{self, BufRead};
use std::fs::File;
use std::fs::{self, File};
use std::ops;
#[test]
@ -114,11 +111,8 @@ where
let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len();
// Read BLT
let file = File::open("tests/data/linn07.blt").expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let mut election: Election<N> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let file = fs::read_to_string("tests/data/linn07.blt").expect("IO Error");
let mut election: Election<N> = Election::from_blt(&file);
// !!! FOR SCOTTISH STV !!!
election.normalise_ballots();

View File

@ -21,8 +21,7 @@ use opentally::stv;
use csv::StringRecord;
use std::fs::File;
use std::io::{self, BufRead};
use std::fs;
use std::ops;
#[allow(dead_code)] // Suppress false positive
@ -48,11 +47,9 @@ where
let stages: Vec<usize> = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect();
// Read BLT
let file = File::open(blt_file).expect("IO Error");
let file_reader = io::BufReader::new(file);
let lines = file_reader.lines();
let file = fs::read_to_string(blt_file).expect("IO Error");
let election: Election<N> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
let election: Election<N> = Election::from_blt(&file);
// Validate candidate names
for (i, candidate) in candidates.iter().enumerate() {