← ↑ → csv2candidates.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from csv2candidates.awk
#! /bin/gawk -f
# Program : csv2candidates.awk
# Purpose : Generate copy'n'paste-able code for Poliblog's candidate lists
# Author : Bob Jonkman bjonkman@sobac.com
# Date : 30 April 2022
# Copyright (c) 2020 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Usage : gawk -f csv2candidates.awk candidates.csv
# Fields : (fields with * will appear even when empty, social media fields only for those who have them)
# Static Fields: (update the variable in BEGIN block when this changes!)
# * 1 Name
# * 2 Riding
# 3 Party # \
# 4 PartyURL # > Party info labels appear only if there's party info, so this can be used for municipal elections too
# 5 PartyLogo # /
# * 6 Website
# * 7 Email
# * 8 Phone
# * 9 Address (single line monolithic, at the moment. May use sub-fields later)
# * 10 ImageURL
# 11 Incumbent
# * 12 RidingURL
# * 13 RidingLogo
#
# Dynamic Fields:
# "Twitter","Facebook","Instagram","YouTube","LinkedIn", &c.
# (social media URLs) field numbers may vary (will eventually be parsed for @Twitter and @instagram)
@include "library.awk"
function makeid(candidatenumber) {
return(valnameid(candidates[candidatenumber,"Name"] "-" candidates[candidatenumber,"Riding"] "-" candidates[candidatenumber,"Party"]))
}
# ImageUrl() added 27 Feb 2025 --Bob
function ImageUrl(UrlString) {
# Dealing with ImageUrl shortcut, ie. "bob-jonkman.jpg" instead of "https://poliblog.jonkman.ca/Poliblog-Elections/images/bob-jonkman.jpg"
if (tolower(substr(UrlString,1,4)) != "http")
return ("../images/" UrlString)
else
return (UrlString)
}
BEGIN {
## Special Purpose fields are ignored in the candidates' list of resources
### Some are used for images, some for class names (CSS styling), some for page layout
skipAttributes["Name"]=1 # Used for header, ID
skipAttributes["Firstname"]=1 # Waterloo Region Votes needs separated FirstName, Lastname (we don't)
skipAttributes["Lastname"]=1 # Waterloo Region Votes needs separated FirstName, Lastname (we don't)
skipAttributes["Riding"]=1 # Used for header, ID
skipAttributes["RidingURL"]=1 # URL to Elections Ontario/Canada website
skipAttributes["RidingLogo"]=1 # URL to image
skipAttributes["RidingShortName"]=1 # Selector for Riding Name
skipAttributes["Party"]=1 # Party name
skipAttributes["PartyShortName"]=1 # Selector for Party name
skipAttributes["PartyLogo"]=1 # URL to image
skipAttributes["PartyURL"]=1 # URL to party website
skipAttributes["Office"]=1 # Used for grouping, ID (eg. Mayor, Councillor, Trustee)
skipAttributes["Elect"]=1 # Additional info for Office
skipAttributes["Municipality"]=1 # Used for grouping, ID
skipAttributes["MunicipalityURL"]=1 # URL to municipal website
skipAttributes["MunicipalityLogo"]=1 # URL to image
skipAttributes["ImageURL"]=1 # URL to candidate image
skipAttributes["Address"]=1 # Used for OpenStreetMap lookup
skipAttributes["Unregistered"]=1 # Show only "Registered", not its negated value
skipAttributes["HiddenNotes"]=1 # 'cos they're hidden, eh?
skipAttributes["Incumbent"]=1 # Class name (styling), indicator
skipAttributes["candidateclass"]=1 # Used internally to transfer styling from table to candidate list
skipAttributes["Display"]=1 # If set, suppress the display of this candidate altogether (so this attribute wouldn't be shown anyway)
### classarray to turn candidate attributes in to a class for styling
classarray["Incumbent"]=1 # for building the class= attribute
classarray["Withdrawn"]=1
classarray["Unregistered"]=1
classarray["Elected"]=1
### Fields to concatenate for search
concatfields["Name"]=1
concatfields["Office"]=1
concatfields["Municipality"]=1
concatfields["Riding"]=1
concatfields["Party"]=1
IFS = ","
OFS = " "
}
# Read the title and subtitle(s)
(NR == 1) {
h_num = parsecsv($0, h)
}
# Read field headers
(NR == 2) {
numfields = getheaders($0, headernums, headernames)
}
# Read rest of the file
(NR > 2) {
numfields = parsecsv($0, inputarray)
for (field = 1; field <= numfields; field++) {
candidates[NR, headernames[field]] = inputarray[field]
}
### Create negated attribute for "Registered==FALSE" to "Unregistered=TRUE"
if ("false" == tolower(candidates[NR, "Registered"]) || "0" == candidates[NR, "Registered"]) {
candidates[NR, "Unregistered"] = "TRUE"
}
### Create an id attribute, same as candidates-search.awk
concatid = ""
for ( concatname in concatfields ) {
if ( "" != trim(candidates[NR, concatname]) ) {
concatid = concatid candidates[NR, concatname] "-"
}
}
candidates[NR, "concatid"] = valnameid(concatid) ### ensure this string is a valid id attribute
### Build candidateclass for styling in table and name
candidateclass = ""
for ( classflag in classarray ) {
if ( "" != trim(candidates[NR, classflag]) ) {
candidateclass = candidateclass " " classflag
}
}
candidates[NR, "candidateclass"] = tolower(candidateclass)
### Build list of ridings
if ( "" != trim(candidates[NR, "Riding"]) ) {
riding = candidates[NR, "Riding"]
ridings[riding]++
ridingurls[riding] = candidates[NR, "RidingURL"]
ridinglogos[riding] = candidates[NR, "RidingLogo"]
} # if ("" != Riding)
### Build list of parties
if ( "" != trim(candidates[NR, "Party"]) ) {
parties[candidates[NR,"Party"]]++ # Build an array of parties
partyurls[candidates[NR,"Party"]] = candidates[NR, "PartyURL"]
ridingsxparties[riding, candidates[NR,"Party"]] = candidates[NR,"Name"] # Build an array of Ridings x Parties
} # if ("" != Party)
}
END {
printhtmlhead(h[1] " - " h[2], "noclose")
##### Instead of "noclose" use
print (" <link rel=\"stylesheet\" href=\"../candidates.css\" type=\"text/css\">")
print (" <link rel=\"shortcut icon\" href=\"https://poliblog.jonkman.ca/blogs/pollywog-32x32b.jpg\" type=\"image/x-icon\">")
### Close <head> ("noclose")
print ("</head>")
print ("<!--")
print ("This information is provided as-is. Data is public information and not subject to copyright.")
print ("Images and logos are copyright and trademarked by their respective owners; their source is linked in the entry.")
print ("They are used under \"Fair Dealing\" as news reporting.")
print ("")
print ("Other material on this page is Copyright (c) " strftime("%Y") " by Bob Jonkman")
print ("and released under a Creative Commons Attribution-only Share-Alike license: http://creativecommons.org/licenses/by-sa/4.0/")
print (" -->")
print ("<body>")
print ("<div id=\"top_of_page\" class=\"header\">")
print (" <!--#include virtual=\"header.ssi\" -->")
print ("</div> <!-- class=header -->")
for (i = 1; i <= h_num; i++) {
if ("" != trim(h[i])) {
(i > 6) ? hval=6 : hval=i
print ("<h" hval ">" makehtml(h[i]) "</h" hval ">")
}
}
# Get ridings and parties in alphabetical order
numridings = asorti(ridings)
numparties = asorti(parties)
# Create table of ridings and parties
# ########################################## #
# #
# This is the new way of creating a table #
# Ridings x Party #
# (because the number of ridings will #
# be more consistent than the number #
# of parties) #
# #
# ########################################## #
print (" <table")
print (" class=\"candidates\"")
print (">")
print (" <thead>")
print (" <tr>")
print (" <th> </th>") # for the Parties column
### Print column headers
for( riding=1; riding<=numridings ; riding++) {
print (" <th><a class=\"internal\" href=\"#" valnameid(ridings[riding]) "\" title=\"Jump to " ridings[riding] "\">" ridings[riding] "</a></th>")
}
print " </tr>"
print " </thead>"
print " <tbody>"
for ( party=1; party<=numparties ; party++ ) {
print " <tr>"
if ( "" == partyurls[parties[party]] ) {
print (" <th>" parties[party] "</th>")
} else {
### The PartyURL field may contain multiple URLs, use only the first one
split(partyurls[parties[party]],partyurlsplit," ")
print (" <th><a href=\"" partyurlsplit[1] "\" title=\"" partyurls[parties[party]] "\">" parties[party] "</a></th>")
} # if ("" = partyurl)
for ( riding in ridings ) {
print " <td>"
for (candidate = 3 ; candidate <= NR ; candidate++ ) {
### Don't display these candidates where Display=False OR where candidate is blank
if ( ("FALSE" != candidates[candidate, "Display"]) && \
("" != trim(candidates[candidate, "Name"])) )
{
if ( (parties[party] == candidates[candidate, "Party"] ) && \
(ridings[riding] == candidates[candidate, "Riding"]) ) {
print "<a href=\"#" candidates[candidate, "concatid"] "\" title=\"" candidates[candidate, "Name"] "\" class=\"internal " candidates[candidate, "candidateclass"] "\">" candidates[candidate, "Name"]
for ( classflag in classarray ) {
if ( "" != trim(candidates[candidate, classflag]) ) { # check for non-empty field, contents may not be == classflag
print " (" classflag ")"
} # if (candidate, classarray)
} # for (classflag)
print "</a><br>"
} # if (Party, Riding)
} # if ( "" != candidate )
} # for (candidate)
print " </td>"
} # for (riding)
print " </tr>"
print ""
} # for (party)
print "</tbody>"
print "</table>"
print ""
# ########################################## #
# #
# #
# End of table Ridings x Party #
# #
# #
# ########################################## #
# Print the list of candidates
for (i=1 ; i <= numridings ; i++) { # generate page menu
riding=ridings[i]
print (" <h3 id=\"" valnameid(riding) "\"><a href=\"#top_of_page\" title=\"Go to the top of this page\" class=\"internal\"> ^ </a> " riding)
if ("" != trim(ridingurls[riding]) ) {
print (" <a href=\"" ridingurls[riding] "\" title=\"Information about " riding "\">")
if ("" != trim(ridinglogos[riding]) ) {
print (" <img class=\"icon\" src=\"" ridinglogos[riding] "\" alt=\"Riding logo\"> Info")
} else {
print ("Information about this riding")
}
print (" </a>")
}
print (" </h3>")
print (" <dl>")
for (candidate = 2; candidate <= NR; candidate++) {
if (candidates[candidate, "Riding"] != riding) {
# Only print the candidates in this riding
continue # with next candidate
}
if (candidates[candidate, "Name"] == "") {
# skip blank records
continue # with next candidate
}
if (candidates[candidate, "Display"] == "FALSE") {
# skip DISPLAY==FALSE
continue # with next candidate
}
print ("")
print (" <dt id=\"" candidates[candidate, "concatid"] "\" class=\"h-card\">")
print (" <a href=\"#top_of_page\" title=\"Go to the top of this page\" class=\"internal\"> ^ </a> ")
print "<span class=\"" candidates[candidate, "candidateclass"] "\">" candidates[candidate, "Name"]
for ( classflag in classarray ) {
if ( "" != trim(candidates[candidate, classflag]) ) { # check for non-empty field, contents may not be == classflag
print " <strong>(" classflag ")</strong>"
} # if (candidate, classarray)
} # for (classflag)
print "</span>"
print (" <a href=\"#" candidates[candidate, "concatid"] "\" title=\"Anchor link for " candidates[candidate, "Party"] " candidate in " riding "\">⚓</a>")
print "<a href=\"searchassistant.html#" candidates[candidate, "concatid"] "\" title=\"Find this candidate on the Search Assistant\" class=\"right\">🔎</a>"
print (" </dt>")
print (" <dd class=\"h-card\">")
print (" <div class=\"aside\">")
if ("" != trim(candidates[candidate, "ImageURL"]) ) {
if ("" != trim(candidates[candidate, "Website"]) ) {
### Ensure we only get the first URL when there are multiples
split(candidates[candidate, "Website"],urls," ")
print ("<a href=\"" urls[1] "\" title=\"" gettitle(urls[1]) "\">")
}
print (" <img src=\"" ImageUrl(candidates[candidate, "ImageURL"]) "\" alt=\"" candidates[candidate, "Name"] "\">")
if ("" != trim(candidates[candidate, "Website"]) ) {
print ("</a>")
}
} else {
print (" <p>No Image</p>")
}
print (" </div> <!-- class=aside -->")
print (" <ul>")
if ("" != trim(candidates[candidate, "Party"]) ) {
print (" <li class=\"p-org\"><b>Party</b>: ") # Field label is optional, for municipal elections without party affiliation
if ("" != trim(candidates[candidate, "PartyURL"]) ) {
### Ensure we only get the first URL when there are multiples
split(candidates[candidate, "PartyURL"],urls," ")
print (" <a href=\"" urls[1] "\" title=\"" gettitle(candidates[candidate, "PartyURL"]) "\">" candidates[candidate, "Party"] " ")
if ("" != trim(candidates[candidate, "PartyLogo"]) ) {
print (" <img src=\"" ImageUrl(candidates[candidate, "PartyLogo"]) "\" alt=\"Logo for " candidates[candidate, "Party"] "\">")
} # if ("PartyLogo")
print (" </a>")
} else { # if no ("PartyURL")
print (candidates[candidate, "Party"] )
} # endif ("PartyURL")
print (" </li>")
} # if ("Party")
# Add social media fields
for (field = 1; field <= numfields; field++) {
if ( headernames[field] in skipAttributes) { # if Attribute is in skipAttributes
continue # don't list static attributes
} # if (Attribute ~ skipAttributes)
if ("" != trim(candidates[candidate, headernames[field]]) ) { # Skip empty fields
print (" <li><b>" headernames[field] "</b>: " makehtml(candidates[candidate, headernames[field]]) "</li>")
} # empty field
} # for (field <= numfields)
# Address at the end (and if not empty) Note: Could be a postal address, could be a Campaign Office address
if ("" != candidates[candidate, "Address"]) {
cleanaddress = stripcomments(trim(candidates[candidate, "Address"]),"(",")")
### Automatically fetch OSM link?
print (" <li class=\"h-adr\"><b>Address</b>:")
print (" <p>")
### If we parse out address info
### print("<!-- <span class=\"p-street-address\">XXXXXSTREETADDRESS</span>,<br><span class=\"p-locality\">XXXXXLOCALITY</span>, <span class=\"p-region\">Ontario</span>,<br><span class=\"p-country\">Canada</span> <span class=\"p-postal-code\">XXXXXPOSTALCODE</span> -->") ;
### If we DON'T parse out address info (monolithic addresses)
print (" <span class=\"p-address\">" candidates[candidate, "Address"] "</span> <a class=\"map\" href=\"https://www.openstreetmap.org/search?query=" txt2uri(cleanaddress) "\" title=\"OpenStreetMap: " cleanaddress "\">Map</a>")
print (" </p>")
print (" </li>")
} # address field not empty
print ("")
print (" </ul>")
print (" <br style=\"clear:both;\">") # Ensure images don't extend beyond <dl> box
print (" </dd>")
print ("")
} # for candidate in candidates
print (" </dl>")
print ("")
} # for riding in ridings
print ("<div class=\"footer\">")
print (" <!--#include virtual=\"footer.ssi\" -->")
print (" <p>Source code for csv2candidates generator: <a href=\"../csv2candidates.html\" title=\"Code Files - csv2candidates.awk and library.awk\">csv2candidates.awk and library.awk</a></p>")
print (" <p><a property=\"dct:title\" rel=\"cc:attributionURL\" href=\"http://poliblog.jonkman.ca/Poliblog-Elections/\">List of Candidates</a> by <a rel=\"cc:attributionURL dct:creator\" property=\"cc:attributionName\" href=\"http://poliblog.jonkman.ca/blogs/about/\">Bob Jonkman</a> is licensed under <a href=\"http://creativecommons.org/licenses/by-sa/4.0/?ref=chooser-v1\" target=\"_blank\" rel=\"license noopener noreferrer\">CC BY-SA 4.0<img alt=\"\" src=\"https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1\"><img alt=\"\" src=\"https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1\"><img alt=\"\" src=\"https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1\"></a></p>")
print (" <p class=\"bottom\">This page was last updated <!--#config timefmt=\"%a, %d %b %Y %T %z\" --><!--#echo var=\"LAST_MODIFIED\"--> </p>")
print ("</div> <!-- class=footer -->")
print ("</body>")
print ("</html>")
print ("")
} # END
# EOF: csv2candidates.awk
← ↑ → csv2municipalcandidates.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from csv2municipalcandidates.awk
#! /bin/gawk -f
# Program : csv2municipalcandidates.awk
# Purpose : Generate copy'n'paste-able code for Poliblog's candidate lists for municipal elections
# Author : Bob Jonkman bjonkman@sobac.com
# Date : 2 July 2022
# Based on : csv2municipalcandidates.awk (for provincial and federal elections with Offices)
# Copyright (c) 2020 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Usage : gawk -f csv2municipalcandidates.awk candidates.csv
# Records : 1st line: Fields are headers <h1> to <h6>
# : 2nd line: Field header names
# : 3rd line to end: Data
# Fields : Fieldnames with * are required, cannot be empty. Other fields appear only if not empty
# Fieldnames with ! are used to group entries and are used for layout. Fieldnames with ! must exist, fields may be empty. Itemized in variable skipAttributes, used to skip in attribute lists
# Fields are referenced by header name, the second line MUST be a field header line
#
# Static Fields (must exist, if blank the entry is ignored):
# 1 Name
# 2 Office
# 3 Municipality
# Address (single line monolithic, at the moment. May use sub-fields later)
# Website
# E mail
# Phone
# "Twitter","Facebook","Instagram","YouTube","LinkedIn", &c.
# (social media URLs) field numbers may vary (will eventually be parsed for @Twitter and @instagram)
@include "library.awk"
function makeid(candidatenumber)
{
# Trailing dash is an artifact of how the Search Assistant creates an id value
return (valnameid(Candidates[candidatenumber]["Name"] "-" Candidates[candidatenumber]["Municipality"] "-" Candidates[candidatenumber]["Office"] "-"))
}
BEGIN {
# IFS = ","
# OFS = " "
# IGNORECASE = 1 # for sorting
# staticfields = 7
# Municipalities[0][0]="" # Initialize 2D array
## Special Purpose fields are ignored in the candidates' list of resources
### Some are used for images, some for class names (CSS styling), some for page layout
skipAttributes["Name"]=1 # Used for header, ID
skipAttributes["Office"]=1 # Used for grouping, ID
skipAttributes["Elect"]=1 # Additional info for Office
skipAttributes["Municipality"]=1 # Used for grouping, ID
skipAttributes["MunicipalityURL"]=1 # URL to municipal website
skipAttributes["MunicipalityLogo"]=1 # URL to image
skipAttributes["ImageURL"]=1 # URL to image
skipAttributes["Address"]=1 # Used for OpenStreetMap lookup
skipAttributes["HiddenNotes"]=1 # Because they're hidden, eh?
skipAttributes["Incumbent"]=1 # Class name (styling), indicator
classarray["Incumbent"]=1 # for building the class= attribute
# skipAttributes["Not_Running"]=1 ### Changed to include reason for withdrawal, eg. "Deceased", so should be shown in candidate info
classarray["Not_Running"]=1
# skipAttributes["Elected"]=1 # Class name (styling), indicator ### Modified 2022-10-24 to show "Elected" or "Acclaimed"
classarray["Elected"]=1
skipAttributes["Unregistered"]=1 # Class name (styling), indicator (set by Registered == "" )
classarray["Unregistered"]=1
skipAttributes["candidateclass"]=1 # Used internally to transfer styling from table to candidate list
} # BEGIN
# Read the title and subtitle(s)
(NR == 1) {
numheadings = parsecsv($0,heading)
} # (NR == 1)
# Read field headers
(NR == 2) {
numfields = getheaders($0, headernums, headernames)
} # (NR == 2)
# Read rest of the file
(NR > 2) {
numfields = parsecsv($0, inputarray)
## Move input line to candidates record
for (field = 1; field <= numfields; field++) {
Candidates[NR][headernames[field]] = inputarray[field]
}
## Populate Municipalities arrays
if ("" != Candidates[NR]["Name"]) {
Municipalities[Candidates[NR]["Municipality"]]++
MunicipalitiesURL[Candidates[NR]["Municipality"]] = Candidates[NR]["MunicipalityURL"]
MunicipalitiesLogo[Candidates[NR]["Municipality"]] = Candidates[NR]["MunicipalityLogo"]
## Populate Offices arrays
Offices[Candidates[NR]["Office"]]++
Elect[Candidates[NR]["Office"],Candidates[NR]["Municipality"]] = Candidates[NR]["Elect"] # Build an array of Offices, use "Elect" for later use
CandidatesInOffice[Candidates[NR]["Office"],Candidates[NR]["Municipality"]]++
} # if ("" != Candidates[NR]["Name"]
}
# (NR > 2)
END {
printhtmlhead(heading[1] " - " heading[2], "noclose")
print (" <link rel=\"stylesheet\" href=\"../candidates.css\" type=\"text/css\">")
print ("</head>")
print ("<!--")
print ("This information is provided as-is. Data is public information and not subject to copyright.")
print ("Images and logos are copyright and trademarked by their respective owners; their source is linked in the entry.")
print ("They are used under \"Fair Dealing\" as news reporting.")
print ("")
print ("Other material on this page is Copyright (c) " strftime("%Y") " by Bob Jonkman")
print ("and released under a Creative Commons Attribution-only Share-Alike license: http://creativecommons.org/licenses/by-sa/4.0/")
print (" -->")
print ("<body>")
print ("<div id=\"top_of_page\" class=\"header\">")
print (" <!--#include virtual=\"header.ssi\" -->")
print ("</div> <!-- class=header -->")
hlevel = 1
for (hnum=1 ; hnum <= numheadings; hnum++) {
if ("" != trim(heading[hnum]) )
print " <h" hlevel ">" makehtml(heading[hnum]) "</h" hlevel ">"
hlevel<=6 ? hlevel++ : 6
}
# strftime parameters: %l=12hour, %M=Minute, %P=ampm, %Z=Timezone, %A=Weekday,%e=DoM, %B=Month
print ("<p>This page was updated at " strftime("%l:%M%P (%Z) %A %e %B %Y") "</p>")
# Get Municipalities and Offices in alphabetical order
numMunicipalities = asorti(Municipalities)
numOffices = asorti(Offices)
# Create table of Municipalities and Offices
print (" <table")
print (" class=\"candidates\"")
print (">")
print (" <thead>")
print (" <tr>")
print (" <th> </th>")
for (Municipality = 1; Municipality <= numMunicipalities; Municipality++) {
if ("" != Municipalities[Municipality]) {
print (" <th><a class=\"internal\" href=\"#" valnameid(Municipalities[Municipality]) "\" title=\"Jump to " Municipalities[Municipality] "\">" Municipalities[Municipality] "</a></th>")
} # if ("" == Municipalities[])
} # for Municipality in Municipalities[]
print (" </tr>")
print (" </thead>")
print (" <tbody>")
for (Office = 1; Office <= numOffices; Office++) {
if ("" != Offices[Office]) {
print (" <tr>")
print (" <th>" Offices[Office] "</th>")
# list candidates for Municipality here
for (Municipality = 1; Municipality <= numMunicipalities; Municipality++) {
if ("" != Municipalities[Municipality]) {
print " <td>"
for (Candidate in Candidates) {
if ((Candidates[Candidate]["Municipality"] == Municipalities[Municipality]) && (Candidates[Candidate]["Office"] == Offices[Office])) {
candidateclass = " " # clear previous value, space to provide separator between existing class string
# Sort out Withdrawn and Registered classes before assigning candidate class
## If the candidate is withdrawn then there is no Registered or Unregistered
if (Candidates[Candidate]["Not_Running"]) {
Candidates[Candidate]["Unregistered"] = ""
} else {
if (Candidates[Candidate]["Registered"]) {
Candidates[Candidate]["Unregistered"] = "" # If Registered exists then clear Unregistered
} else {
Candidates[Candidate]["Unregistered"] = "Unregistered"
} # if Registered
} # if Withdrawn
# Construct a class for this candidate (used for styling)
for (classflag in classarray) {
if ("" != Candidates[Candidate][classflag]) {
candidateclass = candidateclass " " tolower(classflag)
} # if "" != classflag
} # for classflag in classarray
Candidates[Candidate]["candidateclass"] = candidateclass # save for use in list of candidates
print "<a href=\"#" makeid(Candidate) "\" title=\"" Candidates[Candidate]["Name"] "\" class=\"internal " candidateclass " \" >" Candidates[Candidate]["Name"]
if (Candidates[Candidate]["Incumbent"]) {
print " (Incumbent)"
}
if (Candidates[Candidate]["Not_Running"]) {
print " (" Candidates[Candidate]["Not_Running"] ")" # Might contain "Deceased" or other text (which will appear in the candidate's entry)
}
if (Candidates[Candidate]["Unregistered"]) {
print " (Unregistered)"
} # if "" != Unregistered
if (Candidates[Candidate]["Elected"]) {
print " (" Candidates[Candidate]["Elected"] ")" # Might contain "Elected" or "Acclaimed"
}
print "</a><br>"
} # if (candidate matches)
} # for (Candidate in Candidates)
print (" </td>")
} # if ("" != Municipalities[])
} # for (Municipality in Municipalities)
print (" </tr>")
} # if ("" != Offices[Office]["Name"])
} # for (Office in Offices)
print (" </tbody>")
print (" </table>")
# Create the list of candidates
for (Municipality = 1; Municipality <= numMunicipalities; Municipality++) {
if ("" != Municipalities[Municipality]) {
print (" <h3 id=\"" valnameid(Municipalities[Municipality]) "\"><a href=\"#top_of_page\" title=\"Go to the top of this page\" class=\"internal navaid\"> ^ </a> " Municipalities[Municipality])
if ("" != MunicipalitiesURL[Municipalities[Municipality]]) {
print (" <a href=\"" MunicipalitiesURL[Municipalities[Municipality]] "\" title=\"Information about " Municipalities[Municipality] "\">")
if ("" != MunicipalitiesLogo[Municipalities[Municipality]]) {
print (" <img class=\"icon\" src=\"" MunicipalitiesLogo[Municipalities[Municipality]] "\" alt=\"Municipality logo\"> Info")
} else {
print ("Information about " Municipalities[Municipality] "")
} # if (Municpalities logo exists)
print (" </a>")
} # if (Municipalities URL exists)
print (" </h3>")
### Construct an array of Candidates in Office so we avoid printing Offices with no candidates (or Offices that don't apply to a Muncipality)
# for ( Candidate in Candidate ) {
# if ( Candidates[Candidate]
for (Office = 1; Office <= numOffices; Office++) {
if ( CandidatesInOffice[Offices[Office],Municipalities[Municipality]] ) { # If there is at least one candidate running for this office
print "<h4 id=\"" valnameid(Offices[Office] "-" Municipalities[Municipality]) "\">"
print "<a href=\"#top_of_page\" title=\"Go to the top of this page\" class=\"internal navaid\"> ^ </a> "
print " <a href=\"#" valnameid(Municipalities[Municipality]) "\" title=\"Up to the start of " Municipalities[Municipality] "\" class=\"internal navaid\"> ⇑ </a> "
print Offices[Office] " - " Municipalities[Municipality]
if ( Elect[Offices[Office],Municipalities[Municipality]] > 0 ) {
print " : Elect " Elect[Offices[Office],Municipalities[Municipality]]
} # if ( Elect > 0 )
print "</h4>"
print "<dl>"
for (Candidate in Candidates) {
if (("" != Candidates[Candidate]["Name"]) && (Candidates[Candidate]["Municipality"] == Municipalities[Municipality]) && (Candidates[Candidate]["Office"] == Offices[Office])) {
print " <dt id=\"" makeid(Candidate) "\" class=\"h-card\">"
print "<a href=\"#top_of_page\" title=\"Go to the top of this page\" class=\"internal navaid\"> ^ </a> "
print "<a href=\"#" valnameid(Municipalities[Municipality]) "\" title=\"Up to the start of " Municipalities[Municipality] "\" class=\"internal navaid\"> ⇑ </a> "
print "<a href=\"#" valnameid(Offices[Office] "-" Municipalities[Municipality]) "\" title=\"Up to " Offices[Office] " - " Municipalities[Municipality] "\" class=\"internal navaid\" > ↑ </a> "
print "<span class=\"" Candidates[Candidate]["candidateclass"] "\">" Candidates[Candidate]["Name"]
if (Candidates[Candidate]["Not_Running"]) {
print " <strong>(" Candidates[Candidate]["Not_Running"] ")</strong>"
} else { # Registered status is only valid if the candidate is not withdrawn
if (Candidates[Candidate]["Registered"] == "") {
print " <strong>(Unregistered)</strong>"
}
}
if (Candidates[Candidate]["Incumbent"]) {
print " <strong>(Incumbent)</strong>"
}
if (Candidates[Candidate]["Elected"]) {
print " <strong>(" Candidates[Candidate]["Elected"] ")</strong>"
}
print "</span>"
print (" <a href=\"#" makeid(Candidate) "\" title=\"Anchor link for " Candidates[Candidate]["Office"] " candidate in " Municipalities[Municipality] " \" class=\"internal\">⚓</a>")
print "<a href=\"searchassistant.html#" makeid(Candidate) "\" title=\"Find this candidate on the Search Assistant\" class=\"right\">🔎</a>"
print " </dt>"
print (" <dd class=\"h-card\">")
print (" <div class=\"aside\">")
if ("" != Candidates[Candidate]["ImageURL"]) {
if ("" != Candidates[Candidate]["Website"]) {
### Ensure we only get the first URL when there are multiples
split(Candidates[Candidate]["Website"], urls, " ")
print ("<a href=\"" urls[1] "\" title=\"" gettitle(urls[1]) "\">")
} # if ("" != Candidates[Website])
print (" <img src=\"" Candidates[Candidate]["ImageURL"] "\" alt=\"" Candidates[Candidate]["Name"] "\">")
if ("" != Candidates[Candidate]["Website"]) {
print ("</a>")
} # if ( "" != Candidates[Website])
} else { # if ("" != Candidates[ImageURL])
print (" <p>No Image</p>")
} # if ("" != Candidates[ImageURL])
print (" </div> <!-- class=aside -->")
print (" <ul>")
### Print candidatae attributes
for (Attribute in Candidates[Candidate]) {
if (Attribute in skipAttributes) { # if Attribute is in skipAttributes
continue # don't list static attributes
} # if (Attribute ~ skipAttributes)
if ("" != Candidates[Candidate][Attribute]) {
print " <li><b>" Attribute "</b>: " makehtml(Candidates[Candidate][Attribute]) "</li>"
} # if ("" != Candidates[Attribute])
} # (for Attribute in Candidates)
if ("" != Candidates[Candidate]["Address"]) {
### Automatically fetch OSM link
print (" <li class=\"h-adr\"><b>Postal Address</b>:")
### If we parse out address info
### print (" <p style=\"margin-left:2em;\">")
### print("<!-- <span class=\"p-street-address\">XXXXXSTREETADDRESS</span>,<br><span class=\"p-locality\">XXXXXLOCALITY</span>, <span class=\"p-region\">Ontario</span>,<br><span class=\"p-country\">Canada</span> <span class=\"p-postal-code\">XXXXXPOSTALCODE</span> -->") ;
### print (" </p>")
### If we DON'T parse out address info (monolithic addresses)
print (" <span class=\"p-address\">" Candidates[Candidate]["Address"] "</span> <a class=\"map\" href=\"https://www.openstreetmap.org/search?query=" txt2uri(Candidates[Candidate]["Address"]) "\" title=\"OpenStreetMap: " Candidates[Candidate]["Address"] "\">Map</a>")
print (" </li>")
}
print " </ul>"
print (" <br>") # Ensure images don't extend beyond <dl> box
print (" </dd>")
print ("")
} # if ("" != Candidates[Name])
} # for candidate in candidates
print (" </dl>")
print ("")
} # if ("" != Offices[])
} # if (CandidatesInOffice > 0 )
} # if ("" != Municipalities[[])
} # for (Municipality in Municipalities)
print ("<div class=\"footer\">")
print (" <!--#include virtual=\"footer.ssi\" -->")
print (" <p class=\"bottom\">This page was last updated <span class=\"datetime\" title=\"" strftime("%FT%T%z") "\">" strftime("%A, %e %B %Y at %l:%M%P %Z") "</span></p>")
print ("</div> <!-- class=footer -->")
print ("</body>")
print ("</html>")
print ("")
} # END
# EOF: csv2municipalcandidates.awk
← ↑ → csv2candidates-search.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from csv2candidates-search.awk
#! /bin/gawk -f
# Program : csv2candidates-search.awk
# Purpose : Generate a table with links to simplify searching for missing data
# Author : Bob Jonkman bjonkman@sobac.com
# Date : 15 August 2022
# Based on : csv2municipalcandidates.awk
# Modified : 14 Nov 2023 - Added "Party" and "Riding" to id and search term
# 23 Nov 2023 - Create columns for all services we know
# 13 May 2026 - Moved code to generate page headers and start of table to (NR == 2)
# - Made concatfields and xxx deterministic (always in same order, name first)
# - Add headings from first row
# Copyright (c) 2020-2022 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Usage : gawk -f csv2municipalcandidates-search.awk candidates.csv
# Records : 1st line: Fields are headers <h1> to <h6>
# : 2nd line: Field header names
# : 3rd line to end: Data
# Fields : Fieldnames with * are required, cannot be empty.
# Other fields appear only if not empty
# Fieldnames with ! are used to group entries and are used for layout.
# Fieldnames with ! must exist, fields may be empty. Itemized in
# variable skipAttributes, used to skip in attribute lists
# Fields are referenced by header name,
# the second line MUST be a field header line
#
# Static Fields (must exist, if blank the entry is ignored):
# 1 Name
# Optional Static Fields (field must exist, but may be blank)
# Address (single line monolithic, at the moment. May use sub-fields later)
# Website
# E mail
# Phone
# Dynamic Fields (field may or may not exist, and may be blank)
# "Twitter","Facebook","Instagram","YouTube","LinkedIn", &c.
# (social media URLs) field numbers may vary (will eventually be parsed for @Twitter and @instagram)
@include "library.awk"
function makeid(candidatenumber, concatfieldname,concatid)
{
concatid=""
for ( concatfieldname in concatfields ) {
if ( "" != inputarray[headernums[concatfieldname]] ) {
}
}
return (valnameid(concatid))
}
function printURL(URL,LinkText,Class)
{
print "<a href=\"" URL "\" title=\"Search: " txt2uri(LinkText) "\" class=\"" Class "\" target=\"_blank\">" txt2html(LinkText) "</a>"
}
function printsearch(URL,LinkText, LinkFrag,LinkArray,URLArray)
{ if ( "" == inputarray[headernums[LinkText]] ) {
Class="red"
}
else {
Class="green"
}
print "<td class=\"searchcell\">"
printURL(URL,LinkText,Class)
if ("green" == Class) { ### Putting this here is a horrible kludge. To fix later. -Bob 2025-02-07
print "<br>"
split(inputarray[headernums[LinkText]],URLArray," ")
LinkFrag = split(URLArray[1],LinkArray,"/")
if ( "" == LinkArray[LinkFrag] ) {
LinkFrag--
}
printURL(URLArray[1],LinkArray[LinkFrag])
}
print "</td>"
}
BEGIN {
# IFS = ","
# OFS = " "
# IGNORE case = 1 # for sorting
# staticfields = 7
# Municipalities[0][0]="" # Initialize 2D array
## Special Purpose fields are ignored in the candidates' list of resources
### Some are used for images, some for class names (CSS styling), some for page layout
# skipAttributes["Name"]=1 # Used for header, ID
skipAttributes["Office"]=1 # Used for grouping, ID
skipAttributes["Elect"]=1 # Additional info for Office
skipAttributes["Municipality"]=1 # Used for grouping, ID
skipAttributes["MunicipalityURL"]=1 # URL to municipal website
skipAttributes["MunicipalityLogo"]=1 # URL to image
skipAttributes["Riding"]=1 # Riding name
skipAttributes["RidingURL"]=1 # URL to Elections Canada/Ontario website
skipAttributes["RidingLogo"]=1 # URL to Elections Canada/Ontario logo
skipAttributes["PartyShortName"]=1 # selector in VLOOKUP
skipAttributes["Party"]=1 # Party Name
skipAttributes["PartyURL"]=1 # URL to Party URL
skipAttributes["PartyLogo"]=1 # URL to Party logo
skipAttributes["ImageURL"]=1 # URL to candidate image
skipAttributes["Incumbent"]=1 # Class name (styling), indicator
skipAttributes["Unregistered"]=1 # Class name (styling), indicator
skipAttributes["Registered"]=1 # Negation of "Unregistered", since Feb 2025
skipAttributes["Elected"]=1 # Class name (styling), indicator
skipAttributes["Withdrawn"]=1
skipAttributes["Address"]=1 # Used for OpenStreetMap lookup
### Things to not search for
skipAttributes["Website"]=1 # (maybe a general websearch, later)
skipAttributes["Notes"]=1
skipAttributes["Calendar"]=1
skipAttributes["Votes"]=1
skipAttributes["HiddenNotes"]=1
### Fields to concatenate for search
concatfields[1]="Name"
concatfields[2]="Municipality"
concatfields[3]="Office"
concatfields[4]="Riding"
concatfields[5]="Party"
} # BEGIN
# Get headings, itle and subtitle(s)
(NR == 1) { numheadings = parsecsv($0,heading)
} # (NR ==1 )
# Read the column headers
(NR == 2) {
numfields = getheaders($0, headernums, headernames)
# Write the headers and table start up front
LINKREL[1]="<link rel=\"stylesheet\" href=\"../candidates.css\" type=\"text/css\">"
LINKREL[2]="<link rel=\"shortcut icon\" href=\"https://poliblog.jonkman.ca/blogs/pollywog-32x32b.jpg\" type=\"image/x-icon\">"
printhtmlhead("Search for Missing Candidate Data | " heading[1] " | " heading[2])
print ("<!--")
print ("This information is provided as-is. Data is public information and not subject to copyright.")
print ("Images and logos are copyright and trademarked by their respective owners; their source is linked in the entry.")
print ("They are used under \"Fair Dealing\" as news reporting.")
print ("")
print ("Other material on this page is Copyright (c) " strftime("%Y") " by Bob Jonkman")
print ("and released under a Creative Commons Attribution-only Share-Alike license: http://creativecommons.org/licenses/by-sa/4.0/")
print (" -->")
print "<body>"
print ("<div id=\"top_of_page\" class=\"header\">")
print (" <!--#include virtual=\"header.ssi\" -->")
print ("</div> <!-- class=header -->")
print " <h1>Search for Missing Candidate Data</h1>"
hlevel = 2
for (hnum=1 ; hnum <= numheadings; hnum++) {
if ("" != trim(heading[hnum]) )
print " <h" hlevel ">" makehtml(heading[hnum]) "</h" hlevel ">"
hlevel<=6 ? hlevel++ : 6
}
print " <p>Filename: " txt2html(ARGV[1]) "<br>"
print " Date: "
system("stat -c %y " ARGV[1] )
print "<br>Generated: " strftime("%FT%T") "</p>"
##### Apparently, borders="" and caption="" is no longer a valid attribute for <table>
print " <table>"
print " <thead>"
print " <tr>"
print " <td>Search: Name, Locality</td>"
print " <td>Phone</td>"
print " <td>Email</td>"
print " <td>Twitter(X)</td>"
print " <td>Bluesky</td>"
print " <td>Facebook</td>"
print " <td>Instagram</td>"
print " <td>LinkedIn</td>"
print " <td>YouTube</td>"
print " <td>Vimeo</td>"
print " <td>Reddit</td>"
print " <td>Pinterest</td>"
print " <td>Flickr</td>"
print " <td>Tumblr</td>"
print " <td>MySpace</td>"
print " <td>TikTok</td>"
print " <td>Twitch</td>"
print " <td>Snapchat</td>"
print " <td>Threads</td>"
print " <td>Fediverse</td>"
print " <td>Wikipedia</td>"
# (add others as they're discovered)
print " </tr>"
print " </thead>"
print " <tbody>"
}
# Read rest of the file
(NR > 2) {
numfields = parsecsv($0, inputarray)
# If name is blank then advance to next record
if ( "" == inputarray[headernums["Name"]] ) {
next
}
print " <tr>"
### No need to step through fields, we only create links for services we know about
### General search (Startpage (was Google)
searchfieldsquote = ""
searchfieldsbr = ""
searchfieldsdash = ""
for ( i=1; i<=length(concatfields); i++) {
if ( "" != inputarray[headernums[concatfields[i]]] ) {
searchfieldsquote = searchfieldsquote txt2uri("\"" inputarray[headernums[concatfields[i]]] "\" ")
searchfieldsbr = searchfieldsbr inputarray[headernums[concatfields[i]]] "<br>"
searchfieldsdash = searchfieldsdash inputarray[headernums[concatfields[i]]] "-"
} # if ("" != inputarray)
} # for (concatfields)
print " <td id=\"" valnameid(searchfieldsdash) "\">"
print "<a href=\"index.html#" valnameid(searchfieldsdash) "\" title=\"Main entry for " searchfieldsdash " \" class=\"right\">⊕</a>"
print "<a href=\"https://www.startpage.com/sp/search?query=" searchfieldsquote "\" title=\"Startpage Search\" target=\"_blank\" >" searchfieldsbr "</a>"
print "<a href=\"#" valnameid(searchfieldsdash) "\" title=\"Search Assistant Anchor for " searchfieldsdash "\" class=\"right internal\">⚓</a>" # UTF 'anchor' glyph
if (inputarray[headernums["Website"]]) {
numWebs = split(inputarray[headernums["Website"]],Webs," ")
for (num in Webs) {
print "<a href=\"" Webs[num] "\" title=\"Go to: " Webs[num] "\" class=\"right\" target=\"_blank\">🔗</a> " # UTF 'link' glyph
} # for (num in Webs)
} # if (inputarray)
print "</td>"
### Phone Number
print "<td class=\"phone\">"
numPhones = split(inputarray[headernums["Phone"]],Phones," ")
for (num in Phones) {
Phone = substr(Phones[num],index(Phones[num],"-")+1)
printURL("https://www.startpage.com/sp/search?query=" txt2uri("\"" Phone "\""), Phone)
print "<br>"
} # for (num in Phones)
print "</td>"
### E-mail
print "<td class=\"email\">"
numEmails = split(inputarray[headernums["Email"]],Emails," ")
for (num in Emails) {
printURL("https://www.startpage.com/sp/search?query=" txt2uri("\"" Emails[num] "\""), Emails[num])
print "<br>"
} # for (num in Emails)
print "</td>"
### Social Media sites
printsearch("https://x.com/search?src=typed_query&f=user&q=" txt2uri(inputarray[headernums["Name"]]), "Twitter(X)")
printsearch("https://bsky.app/search?q=" txt2uri(inputarray[headernums["Name"]]), "Bluesky")
printsearch("https://www.facebook.com/search/top/?q=" txt2uri(inputarray[headernums["Name"]]), "Facebook")
printsearch("https://www.instagram.com/explore/search/keyword/?q=" txt2uri(inputarray[headernums["Name"]]), "Instagram")
printsearch("https://linkedin.com/search/results/people/?keywords=" txt2uri(inputarray[headernums["Name"]]), "LinkedIn")
printsearch("https://www.youtube.com/results?sp=EgIQAg%253D%253D&search_query=" txt2uri(inputarray[headernums["Name"]]), "YouTube")
printsearch("https://vimeo.com/search/q=" txt2uri(inputarray[headernums["Name"]]), "Vimeo")
printsearch("https://www.reddit.com/search/?type=user&q=" txt2uri(inputarray[headernums["Name"]]), "Reddit")
printsearch("https://www.pinterest.ca/search/pins/?q=" txt2uri(inputarray[headernums["Name"]]), "Pinterest")
printsearch("https://www.flickr.com/search/people/?username=" txt2uri(inputarray[headernums["Name"]]), "Flickr")
printsearch("https://www.tumblr.com/search/" txt2uri(inputarray[headernums["Name"]]), "Tumblr")
printsearch("https://myspace.com/search/people?q=" txt2uri(inputarray[headernums["Name"]]), "MySpace")
printsearch("https://www.tiktok.com/search/user?q=" txt2uri(inputarray[headernums["Name"]]), "TikTok")
printsearch("https://www.twitch.tv/search?term=" txt2uri(inputarray[headernums["Name"]]), "Twitch")
printsearch("https://www.snapchat.com/explore/" txt2uri(gensub(" ","-","G",inputarray[headernums["Name"]])) "/profiles", "Snapchat")
printsearch("https://www.threads.net/search?filter=profiles&q=" txt2uri(inputarray[headernums["Name"]]), "Threads")
printsearch("https://thecanadian.social/search?type=accounts&q=" txt2uri(inputarray[headernums["Name"]]), "Fediverse")
printsearch("https://en.wikipedia.org/wiki/Special:Search?search=" txt2uri(inputarray[headernums["Name"]]), "Wikipedia")
print " </tr>"
} # (NR > 2)
END { print " </tbody>"
print " </table>"
print " <div class=\"footer\">"
print (" <!--#include virtual=\"footer.ssi\" -->")
print (" <p class=\"bottom\">This page was last updated <span class=\"datetime\" title=\"" strftime("%FT%T%z") "\">" strftime("%A, %e %B %Y at %l:%M%P %Z") "</span></p>")
print (" </div> <!-- class=footer -->")
print ("</body>")
print ("</html>")
print ("")
}
# EOF: csv2candidates-search.awk
← ↑ → candidates.css
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from candidates.css
/* ******************************************************** *\
** File : candidates.css
** Purpose : Stylesheet for Candidates contact list
** Author : Bob Jonkman bjonkman@sobac.com
** Date : 2 May 2022
\* ******************************************************** */
body { background-image: none ; /* override default.css */
}
/* The Party Logo */
.p-org img { width: auto ;
max-width: 140px ;
height: auto ;
max-height: 140px ;
background: #888 ;
border: thin solid black ;
float: right ;
margin-right: 5em;
margin-left: 1em ;
}
img.icon { margin: 0 .5em;
vertical-align: middle;
height: 3ex;
width: auto ;
/* padding: 0 .3em ; */
}
.aside { text-align: center ;
}
.aside img { max-width : 150px ;
max-height: 150px ;
margin-left: auto ;
margin-right: auto ;
}
dl li { margin-bottom: 2.5ex ; /* line spacing */
list-style-type: none ;
}
dd br { clear: both ;
}
table { font-size: small ;
}
td { background: #eee ;
}
/* Put each candidate link in a full-cell-width box */
table.candidates td a { display: block ;
margin: .1ex 0 ;
border: thin solid black ;
padding: .3em ;
background: white ;
}
td br { height: 0 ;
margin: 0 ;
}
/* in searchassistant.html */
td.searchcell { text-align:center ;
}
/* Candidates name in main candidate list */
dt.h-card span { padding: 0 .3em ;
}
/* Flags */
.incumbent
{ outline: thick solid fuchsia ;
}
.withdrawn,
.not_running
{ text-decoration: line-through !important ;
background: #CCC !important ; /* medium grey */
}
.unregistered
{ background: orange !important ; /* light orange */
}
.elected
{ background: lime !important ; /* light green */
}
/* EOF: candidates.css */
← ↑ → /home/bjonkman/bin/awk/csv2html.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from /home/bjonkman/bin/awk/csv2html.awk
#! /usr/bin/gawk -f
# Program : CSV2HTML.AWK
# Purpose : Convert a CSV file into an HTML table
# Date : 18 February 2005
# Author : Bob Jonkman <bjonkman@sobac.com>
# Copyright 2008 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Modified : 3 February 2006 - Added navaids "Go to the top of this page"
# 31 March 2006 - Changed default from HTTP to NOHTTP primarily in dynamic pages
# - Added other code (eg. QUERY_STRING) for dynamic pages
# 25 Sept 2006 - Added "Prev, Next" navigational aids
# 9 January 2007 - Added id attributes to all TR elements
# 30 May 2007 - Using tempfile to place tfoot before tbody
# 6 March 2008 - Added table sorting with sorttable.js
# from http://www.kryogenix.org/code/browser/ by Stuart Langridge
# 21 May 2008 - Fixed bug where an empty <tbody> would come from previous file
# (required considerable re-organizing of code)
# - Added "1 file(s) processed" logic
# 4 June 2008 - added SVG object when PLOT==1
# 1 May 2022 - Added "@include library.awk" (is this a new feature?)
# 12 Nov 2023 - Added title to LINK anchor
# - Create variable VeRsIoN, set to 2023-11-12
# 6 Feb 2025 - Cleaned up code to HTML5 standard
# - Removed <table> attributes 'summary' and 'borders'
# - Removed <script> attribute 'type'
# - Removed <a> attribute 'name'
# - Check for existence of ENVIRON["TEMP"] before creating tempfile
BEGIN { VeRsIoN="2025-02-06" ;
}
# Usage : gawk -f CSV2HTML.AWK -f LIBRARY.AWK [-v TITLE=title] [0*(-v variable=(0|1) )] 1*(filename.csv)
# http:\\hostname\csv2html.awk?FILENAME=filename.csv[1*(+filename.csv)][&TITLE=title][0*(&variable(0|1))]
# Note : Use a literal + to separate filenames in URI
# Variables : TITLE = (text) HTML <title> and <h1> elements for the page
# PLOT = if TRUE inserts FILENAME.svg object element with fallback to FILENAME.png
# LINK = if TRUE Creates link to FILENAME for each table
# NOHEADER = if TRUE Does not place the first line of CSV file in THEAD entity
# NOHTTP = if TRUE Suppresses a "Content-type: text/html" line for dynamic pages
# SORTABLE = if TRUE adds script=sorttable.js to head and class="sortable" to tables
#
# Optional vars: FIELD_SEPARATOR (default == ",")
# FIELD_DELIMITER (default == QUOTE )
# RECORD_SEPARATOR (default == "\n")
@include "library.awk" ;
function printbody(printtext) { # Send <tbody> parts to temporary file
if(!tfootflag)
print(printtext) > tempfile ;
else
print(printtext) ;
}
function finishtable() {
if(tfootflag)
{ print(" </tfoot>") ;
}
if(tbodyflag)
{
close(tempfile) ; # stop writing to tempfile
print(" <tbody>") ;
while(getvalue = (getline templine < tempfile) > 0)
{
print(templine) ; # copy all records to standard output
}
close(tempfile) ; # stop reading from file
if(ENVIRON["windir"])
system ("del " tempfile) ; # Clean up after yourself.
else
system ("rm " tempfile) ;
print(" </tbody>") ;
} else
{
if(tableflag) # If a <table> was printed
{ # then print a vestigial <tbody>
print(" <tbody>") ;
print(" <tr>") ;
print(" <td>") ;
print(" </td>") ;
print(" </tr>") ;
print(" </tbody>") ;
}
}
if(tableflag)
{
print(" </table>")
print(" </div> <!-- end of id=" QUOTE valnameid(FILENAME) QUOTE " -->") ;
print("")
}
tfootflag = 0 ;
tbodyflag = 0 ;
tableflag = 0 ;
}
function makemenu(topofpage) {
# Create header/footer menu
# parameter==TRUE then include "Top of page" link
print(" <div class=" QUOTE "menu" QUOTE ) ;
if(!topofpage) # if this is NOT at the top of page then ...
print(" id=" QUOTE "toc" QUOTE ) ;
print(" >")
print(" <p>In this page:</p>") ;
print(" <ul>" )
if (topofpage)
{
print(" <li>[<a href=" QUOTE "#top-of-page" QUOTE " class=" QUOTE "internal" QUOTE ">Top of this page</a>] </li>")
print(" <li>[<a href=" QUOTE "#toc" QUOTE " class=" QUOTE "internal" QUOTE ">Table of Contents</a>] </li>")
}
for(i=1; i<ARGC; i++)
{
print(" <li>[<a class=" QUOTE "internal" QUOTE " href=" QUOTE "#" valnameid(ARGV[i]) QUOTE ">" txt2html(ARGV[i]) "</a>] </li>")
}
print(" </ul>")
print(" </div> <!-- end of menu -->")
}
BEGIN { version = "\nVeRsIoN= csv2html.awk " VeRsIoN "\nCoPyRiGhT= Copyright under GNU GPLv3 2008 by Bob Jonkman bjonkman@sobac.com \n"
QUOTE = "\"" ;
if(!FIELD_SEPARATOR)
FIELD_SEPARATOR = "," ;
if(!FIELD_DELIMITER)
FIELD_DELIMITER = "\"" ;
if(!RECORD_SEPARATOR)
RECORD_SEPARATOR = "\n" ;
##### Find a way to use 'mktemp' instead --Bob 2025-02-06
tempfile = (ENVIRON["TEMP"] ? ENVIRON["TEMP"] "\\" : "") "csv2html" PROCINFO["pid"] ".tmp" ; # Temporary file for storing <tbody> parts
parsecgi(ENVIRON["QUERY_STRING"],qstring)
if(ARGC == 1) # no files on the command line
{
if(qstring["FILENAME"])
{
ARGC = 1 + split(qstring["FILENAME"],ARGV," ")
}
}
if(!TITLE)
TITLE = qstring["TITLE"]
if(!TITLE) # if there is still no title then...
TITLE = "Tables from CSV files"
if(!PLOT)
PLOT = qstring["PLOT"]
if(!LINK)
LINK = qstring["LINK"]
if(!NOHEADER)
NOHEADER = qstring["NOHEADER"]
th = NOHEADER ? "td" : "th" ; # if NOHEADER then first column cell is <td> else <th>
if(!SORTABLE)
SORTABLE = qstring["SORTABLE"]
if(SORTABLE)
LINKREL["sortable"] = "<script src=\"/bin/javascript/sorttable.js\"></script> <!-- from http://www.kryogenix.org/code/browser/ by Stuart Langridge -->" ;
printhtmlhead(TITLE)
print(" <body>") ;
print(" <div>") ;
print(" <a id=" QUOTE "top-of-page" QUOTE ">");
print(" <!-- " version " -->");
print(" </a>") ;
print(" </div>") ;
# print(" <div id=" QUOTE "header" QUOTE ">") ;
# print(" <a href=" QUOTE "/" QUOTE) ;
# print(" title=" QUOTE "" QUOTE ">") ;
# print(" <img") ;
# print(" src=" QUOTE "/images/sitelogo.png" QUOTE ) ;
# print(" alt=" QUOTE "Site logo" QUOTE ) ;
# print(" style=" QUOTE "float: left ; height: 48px ; width: 150px ;" QUOTE "></a>") ;
#
# print(" <a href=" QUOTE "/" QUOTE "") ;
# print(" title=" QUOTE "" QUOTE ">") ;
# print(" <img") ;
# print(" src=" QUOTE "/images/siteicon.png" QUOTE ) ;
# print(" alt=" QUOTE "Site icon" QUOTE ) ;
# print(" style=" QUOTE "float: right ; height: 48px ; width: 48px ;" QUOTE "></a>") ;
#
# print(" <br>") ;
# print(" <hr>") ;
# print(" </div><!-- end of header -->") ;
# print("") ;
print(" <h1>" makehtml(TITLE) "</h1>")
if (ARGC == 1)
{ print(" <div id=" QUOTE "content" QUOTE ">")
print(" <p>No files specified, nothing to do!</p>")
exit # this will process END{}
}
if (ARGC > 2) # If there is more than one file on the page
makemenu(0) ; # create header menu without "Top of page" link
print(" <div id=" QUOTE "content" QUOTE ">")
} # end of BEGIN
(FNR == 1) { # Finish off the previous file
if(ARGIND > 1) # check if we've processed multiple files
finishtable() ;
print(" <div id=" QUOTE valnameid(FILENAME) QUOTE ">") ;
print(" <h2")
if (ARGIND > 1) # Suppress page-break-before on the first file
print(" style=" QUOTE "page-break-before: always ; " QUOTE )
print(" >")
if (ARGC > 2) # Display "Prev, Top, Next" navigational aids
{
print(" <span class=" QUOTE "internal navaid" QUOTE ">")
if (ARGIND > 1) # suppress "Previous" for first file
{
print(" <a href=" QUOTE "#" valnameid(ARGV[ARGIND-1]) QUOTE)
print(" title=" QUOTE "Previous Table: " txt2html(ARGV[ARGIND-1]) QUOTE)
print(" >←</a>")
} else
{ print(" <span class=" QUOTE "disabled" QUOTE ">←</span>")
}
print(" <a href=" QUOTE "#top-of-page" QUOTE)
print(" title=" QUOTE "Go to the top of this page" QUOTE)
print(" > ↑ </a>")
if (ARGIND < ARGC-1 ) # suppress "Next" for last file
{
print(" <a href=" QUOTE "#" valnameid(ARGV[ARGIND+1]) QUOTE)
print(" title=" QUOTE "Next Table: " txt2html(ARGV[ARGIND+1]) QUOTE)
print(" >→</a>")
} else
{ print(" <span class=" QUOTE "disabled" QUOTE ">→</span>")
}
print(" </span>")
}
print(" " txt2html(FILENAME) "</h2>" )
print(" <p>Generated on <abbr class=" QUOTE "datetime" QUOTE " title=" QUOTE strftime("%Y-%m-%dT%H:%M:%S") QUOTE ">" strftime("%a, %d %b %Y %H:%M:%S %z") "</abbr> from ")
if(LINK)
print(" <a href=" QUOTE txt2uri(FILENAME) QUOTE " title=" QUOTE "Download " txt2html(FILENAME) QUOTE ">" txt2html(FILENAME) "</a>")
else
print(" " txt2html(FILENAME))
print(" </p>")
print("")
if(PLOT)
{
print(" <div class=" QUOTE "box svgobject" QUOTE ">") ;
print(" <object data=" QUOTE txt2uri(FILENAME ".svg") QUOTE ) ;
print(" title=" QUOTE "Plot of " txt2html(FILENAME) QUOTE ) ;
print(" type=" QUOTE "image/svg+xml" QUOTE ) ;
print(" >" ) ;
print(" <img src=" QUOTE txt2uri(FILENAME ".png") QUOTE " alt=" QUOTE "Plot of " txt2html(FILENAME ".svg") QUOTE "/>");
print(" </object>") ;
print(" <br>");
print(" <a href=" QUOTE txt2uri(FILENAME ".svg") QUOTE ">");
print(" Plot of " txt2html(FILENAME ".svg") ) ;
print(" </a>") ;
print(" </div><!-- end of svgobject -->");
print("") ;
}
tableflag = 1 ; # Yes, we've started a table
print(" <table")
if (SORTABLE)
print(" class=" QUOTE "sortable" QUOTE ) ;
print(" >")
} # end of (FNR == 1)
((FNR == 1) && (!NOHEADER)) { # Do the headers, except if NOHEADER is specified
print(" <thead>")
print(" <tr id=" QUOTE "line" FNR "file" ARGIND QUOTE ">")
delete hdr
numfields = parsecsv($0,header)
for(i=1; i <= numfields; i++)
{
hdr[i] = "hdr" i "file" ARGIND
print(" <th")
print(" id=" QUOTE hdr[i] QUOTE ) # doesn't need validnameid(hdr[i]) because we've generated hdr[i]
# print(" style=" QUOTE "border: thin solid ;" QUOTE )
print(" >" makehtml(header[i]) "</th>")
}
print(" </tr>")
print(" </thead>")
bodyflag = 1 # reading tbody
next
} #end of ((FNR == 1) && (!NOHEADER))
($0 == "=====") { # My convention for signalling footers in CSV files is "====="
tfootflag = 1 # now writing <tfoot>
print(" <tfoot>") ;
next # Don't display the footer separator itself
} #end of ($0 == "=====")
((FNR > 1) || (NOHEADER)) {
if(!tfootflag) # If we're not writing <tfoot>...
tbodyflag = 1 ; # ... we must be writing <tbody>
numfields = parsecsv($0,row)
printbody(" <tr id=" QUOTE "line" FNR "file" ARGIND QUOTE ) ;
printbody(" class=" QUOTE (FNR % 2 ? "odd" : "even" ) QUOTE ">") ;
printbody(" <" th) # Note that the first element in a column could be replaced with <th></th>
if (hdr[1])
printbody(" headers=" QUOTE hdr[1] QUOTE )
printbody(" >" makehtml(row[1]) "</" th ">")
for(i=2; i <= numfields; i++)
{ printbody(" <td")
if (hdr[i])
printbody(" headers=" QUOTE hdr[i] QUOTE )
printbody(" >" makehtml(row[i]) "</td>")
}
printbody(" </tr>")
} #end of ((FNR > 1) || (NOHEADER))
END { if(ARGIND) # Tables are ended only if files have been processed
{
finishtable() ;
}
print(" <p>" ARGIND " file" ((ARGIND==1) ? "" : "s") " processed.</p>")
print(" </div> <!-- end of content -->")
if (ARGC > 2) # If there is more than one file on the page
{ print(" <div id=" QUOTE "footer" QUOTE ">")
makemenu(1) ; # Create footer menu with "Top of page" link
print(" </div> <!-- end of footer -->")
}
print(" </body>")
print("</html>")
} #end of END
# EOF: CSV2HTML.AWK
← ↑ → /home/bjonkman/bin/awk/code2html.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from /home/bjonkman/bin/awk/code2html.awk
#! /bin/gawk -f
# Program : CODE2HTML.AWK
# Purpose : Convert various program and code files to an HTML page with <code> constructs
# Date : 13 October 2006
# Author : Bob Jonkman <bjonkman@sobac.com>
# Copyright 2008 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Usage : gawk -f CODE2HTML.AWK -f LIBRARY.AWK [-v TITLE=title] [-v LINK=(0|1)] 1*(filename.csv)
# http:\\hostname\code2html.awk?FILENAME=filename.ext[1*(+filename.ext)][&TITLE=title][&LINK=(0|1)]
# Note : Use a + to separate filenames in URI
# Variables : TITLE = HTML <title> and <h1> elements for the page
# LINK = Creates link to FILENAME for each table
# NOHTTP = Suppress a "Content-type: text/html" line for dynamic pages
@include "library.awk"
function makemenu(topofpage) {
# Create header/footer menu
# parameter==TRUE then include "Top of page" link
print(" <div class=" QUOTE "menu" QUOTE ) ;
if(!topofpage) # if this is NOT at the top of page then ...
print(" id=" QUOTE "toc" QUOTE ) ;
print(" >")
print(" <p>In this page:</p>") ;
print(" <ul>" )
if (topofpage)
{
print(" <li>[<a href=" QUOTE "#top-of-page" QUOTE " class=" QUOTE "internal" QUOTE ">Top of this page</a>] </li>")
print(" <li>[<a href=" QUOTE "#toc" QUOTE " class=" QUOTE "internal" QUOTE ">Table of Contents</a>] </li>")
}
for(i=1; i<ARGC; i++)
{
print(" <li>[<a class=" QUOTE "internal" QUOTE " href=" QUOTE "#" valnameid(ARGV[i]) QUOTE ">" txt2html(ARGV[i]) "</a>] </li>")
}
print(" </ul>")
print(" </div> <!-- end of menu -->")
}
BEGIN { QUOTE = "\""
if(!FIELD_SEPARATOR)
FIELD_SEPARATOR = "," ;
if(!FIELD_DELIMITER)
FIELD_DELIMITER = "\"" ;
if(!RECORD_SEPARATOR)
RECORD_SEPARATOR = "\n" ;
parsecgi(ENVIRON["QUERY_STRING"],qstring)
if(ARGC == 1) # no files on the command line
{
if(qstring["FILENAME"])
{
ARGC = 1 + split(qstring["FILENAME"],ARGV," ")
}
}
if(!NOHTTP)
NOHTTP = !(ENVIRON["REQUEST_METHOD"]) ;
if(!TITLE)
TITLE = qstring["TITLE"]
if(!TITLE) # if there is still no title then...
TITLE = "Code Files"
if(!LINK)
LINK = qstring["LINK"]
printhtmlhead(TITLE)
print(" <body>") ;
print(" <div>") ;
print(" <a id=" QUOTE "top-of-page" QUOTE ">");
print(" <!-- " version " -->");
print(" </a>") ;
print(" </div>") ;
print(" <h1>" makehtml(TITLE) "</h1>")
if (ARGC == 1)
{ print(" <div id=" QUOTE "content" QUOTE ">")
print(" <p>No files specified, nothing to do!</p>")
exit # this will process END{}
}
if (ARGC > 2) # If there is more than one file on the page
makemenu(0) ; # create header menu without "Top of page" link
print(" <div id=" QUOTE "content" QUOTE ">")
}
(FNR == 1) {
if(ARGIND > 1) # check if we've processed multiple files
{ print(" </code></pre>")
print(" </div> <!-- end of id=" QUOTE valnameid(ARGV[ARGIND-1]) QUOTE " -->") ;
print("")
}
print(" <div id=" QUOTE valnameid(FILENAME) QUOTE ">") ;
print(" <h2")
if (ARGIND != 1) # Suppress page-break-before on the first file
print(" style=" QUOTE "page-break-before: always ; " QUOTE )
print(" >")
if (ARGC > 2) # Display "Prev, Top, Next" navigational aids
{
print(" <span class=" QUOTE "internal navaid" QUOTE ">")
if (ARGIND != 1) # suppress "Previous" for first file
{
print(" <a href=" QUOTE "#" valnameid(ARGV[ARGIND-1]) QUOTE)
print(" title=" QUOTE "Previous Table: " txt2html(ARGV[ARGIND-1]) QUOTE)
print(" >←</a>")
} else
{ print(" <span class=" QUOTE "disabled" QUOTE ">←</span>")
}
print(" <a href=" QUOTE "#top-of-page" QUOTE)
print(" title=" QUOTE "Go to the top of this page" QUOTE)
print(" > ↑ </a>")
if (ARGIND < ARGC-1 ) # suppress "Next" for last file
{
print(" <a href=" QUOTE "#" valnameid(ARGV[ARGIND+1]) QUOTE)
print(" title=" QUOTE "Next Table: " txt2html(ARGV[ARGIND+1]) QUOTE)
print(" >→</a>")
} else
{ print(" <span class=" QUOTE "disabled" QUOTE ">→</span>")
}
print(" </span>")
}
print(" " txt2html(FILENAME) "</h2>" )
print(" <p>Generated on <abbr class=" QUOTE "datetime" QUOTE " title=" QUOTE strftime("%Y-%m-%dT%H:%M:%S") QUOTE ">" strftime("%a, %d %b %Y %H:%M:%S %z") "</abbr> from ")
if(LINK)
print(" <a href=" QUOTE txt2uri(FILENAME) QUOTE ">" txt2html(FILENAME) "</a>")
else
print(" " txt2html(FILENAME))
print(" </p>")
print(" <pre><code>")
}
# Main : process for every line in the file
{ print(txt2html($0))
}
END { if(ARGIND)
{ print(" </code></pre>")
print(" </div> <!-- end of id=" QUOTE valnameid(FILENAME) QUOTE " -->") ;
}
print(" <p>" ARGIND " files processed.</p>")
print(" </div> <!-- end of content -->")
if (ARGC > 2) # If there is more than one file on the page
{ print(" <div id=" QUOTE "footer" QUOTE ">")
makemenu(1) ; # Create footer menu with "Top of page" link
print(" </div> <!-- end of footer -->")
}
print(" </body>")
print("</html>")
}
# EOF: CODE2HTML.AWK
← ↑ → /home/bjonkman/bin/awk/library.awk
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from /home/bjonkman/bin/awk/library.awk
# Program : LIBRARY.AWK
# Purpose : Contains functions common to many AWK scripts
# Author : Bob Jonkman <bjonkman@sobac.com>
# Copyright 2008 Bob Jonkman and/or SOBAC Microcomputer Services
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Date : 19 July 2005
# Contents :
# rstring() - Returns the right-most n characters of string
# max() - Maximum of items in list
# min() - Minimum of items in list
# trim() - Trim whitespace from both ends of a string
# ltrim() - Trim whitespace from beginning (left side) of a string
# rtrim() - Trim whitespace from end (right side) of a string
# gwid2smtp() - escape reserved SMTP characters in GW Object
# see http://www.novell.com/documentation/gw55/index.html?page=/documentation/gw55/gw55ia/data/a30135u.html#a30135u
# smtp2gwid() - un-escape
# reverse() - reverse order of delimited string, eg. a.b.c -> c_b_a
# txt2html() - escape reserved HTML characters
# txt2xml() - escape reserved XML characters
# html2txt() - restore reserved HTML characters
# txt2uri() - escape reserved URI characters
# uri2txt() - restore reserved URI characters
# makehtml() - Create href links (http:// file:// mailto:) from text (maito: was microformatted with class="vcard" on 2006-08-04)
# parsecsv() - Split Comma-Quote fields in string into an array
# printcsv() - Determine whether to print a field with delimiters
# valnameid() - Validate Name and ID token names (see http://www.w3.org/TR/html4/types.html#type-id )
# printhtmlhead() - print headers for an HTML file
# parsecgi() - Parse QUERY_STRING or POST data into an array
# txt2gwapi() - escape reserved characters in GW API files
# (see pg 42,43 "Keyword Ordering Requirements and Delimiters" in GroupWise API Gateway documentation)
# getheaders() - Create reverse lookup array for input string
# printarray() - Print all elements of an array (for debugging, mostly) Added 2022-05-01
# gettitle() - Get a title attribute from web page (stub added 2022-05-01)
# stripcomments() - Remove text between delimiters (and the delimiters). Added 2025-02-02
function rstring(string,n) { # Returns the right-most n characters of string
return(substr(string,length(string)-n+1))
}
function max(a,b) { return(( a > b ) ? a : b)
}
function min(a,b) { return(( a < b ) ? a : b)
}
function trim(string) {
string = rtrim(string) ;
string = ltrim(string) ;
return(string) ;
}
##### End trim #####
# trim left-side whitespace
function ltrim(string) { gsub(/^[ \t]+/, "", string)
return(string)
}
##### End ltrim #####
# trim right-side whitespace
function rtrim(string) { gsub(/[ \t]+$/, "", string)
return(string)
}
##### End rtrim #####
function gwid2smtp(text, smtptext) # Perform GroupWise character translation
# http://www.novell.com/documentation/gw55/index.html?page=/documentation/gw55/gw55ia/data/a30135u.html#a30135u
{
smtptext = text ;
gsub(/#/ ,"#h#",smtptext) ;
gsub(/_/ ,"#u#",smtptext) ;
gsub(/ / ,"_" ,smtptext) ;
gsub(/\(/ ,"#l#",smtptext) ;
gsub(/)/ ,"#r#",smtptext) ;
gsub(/,/ ,"#m#",smtptext) ;
gsub(/:/ ,"#c#",smtptext) ;
gsub(/\\/ ,"#b#",smtptext) ;
gsub(/=/ ,"#e#",smtptext) ;
gsub(/\// ,"#s#",smtptext) ;
return(smtptext) ;
}
##### End gwid2smpt #####
function smtp2gwid(text, gwidtext)
{
gwidtext = text ;
gsub(/#s#/,"/" ,gwidtext);
gsub(/#e#/,"=" ,gwidtext);
gsub(/#b#/,"\\",gwidtext);
gsub(/#c#/,":" ,gwidtext);
gsub(/#m#/,"," ,gwidtext);
gsub(/#r#/,")" ,gwidtext);
gsub(/#l#/,"(" ,gwidtext);
gsub(/_/ ," " ,gwidtext);
gsub(/#u#/,"_" ,gwidtext);
gsub(/#h#/,"#" ,gwidtext);
return(gwidtext) ;
}
##### End smtp2gwid #####
function reverse(instring,inseparator,outseparator, numelements,array,i,outstring)
{
numelements = split(instring, array, inseparator);
outstring = array[numelements];
for (i=numelements-1; i>0; i--)
outstring = outstring outseparator array[i];
return outstring;
}
##### End reverse #####
function txt2html(text, htmltext)
{
htmltext = text ;
gsub(/&/ , "\\&" ,htmltext)
gsub(/>/ , "\\>" ,htmltext)
gsub(/</ , "\\<" ,htmltext)
gsub(/"/ , "\\"" ,htmltext)
gsub(/'/ , "\\'" ,htmltext) # ' or tick appears to be invalid in XML UTF-8 documents, substitute numeric entity
gsub(/\x91/, "\\‘",htmltext) # ‘ left-single-quote or backtick becomes tick
gsub(/\x92/, "\\’",htmltext) # ’ right-single-quote becomes tick
gsub(/\x93/, "\\“",htmltext) # “ left-double-quote becomes double-quote
gsub(/\x94/, "\\”",htmltext) # ” right-double-quote becomes double-quote
gsub(/\x95/, "\\&ndash" ,htmltext) # – N-dash becomes hyphen (for XML)
gsub(/\x96/, "\\&mdash" ,htmltext) # — EM Dash becomes two hyphens
return(htmltext) ;
}
##### End txt2html #####
function txt2xml(text, xmltext) # converts plain text or HTML to XML
{
xmltext = text ;
gsub(/&/ , "\\&" ,xmltext)
gsub(/>/ , "\\>" ,xmltext)
gsub(/</ , "\\<" ,xmltext)
gsub(/"/ , "\\"" ,xmltext)
gsub(/'/ , "\\'" ,xmltext) # ' or tick appears to be invalid in XML UTF-8 documents, substitute numeric entity
gsub(/\x91/ , "\\'" ,xmltext) # left-single-quote or backtick becomes tick
gsub(/‘/, "\\'" ,xmltext) # ‘ left-single-quote becomes tick
gsub(/\x92/ , "\\'" ,xmltext) # ’ right-single-quote becomes tick
gsub(/’/, "\\'" ,xmltext) # right-single-quote becomes tick
gsub(/\x93/ , "\\"" ,xmltext) # “ left-double-quote becomes double-quote
gsub(/“/, "\\"" ,xmltext) # left-double-quote becomes double-quote
gsub(/\x94/ , "\\"" ,xmltext) # ” right-double-quote becomes double-quote
gsub(/”/, "\\"" ,xmltext) # right-double-quote becomes double-quote
gsub(/\x95/ , "-" ,xmltext) # – N-dash becomes hyphen (for XML)
gsub(/–/, "-" ,xmltext) # – N-dash becomes hyphen (for XML)
gsub(/--/ , "\\+\\+" ,xmltext) # two hyphens are invalid inside comments <!-- -->
gsub(/\x96/ , "\\+\\+" ,xmltext) # — EM Dash becomes two hyphens
gsub(/—/, "\\+\\+" ,xmltext) # — EM Dash becomes two hyphens
return(xmltext) ;
}
function html2txt(htmltext, text)
{
text = htmltext ;
gsub(/–/ , "-" , text)
gsub(/–/ , "-" , text)
gsub(/—/ , "--" , text)
gsub(/—/ , "--" , text)
gsub(/‘/ , "'" , text) # left-single-quote value
gsub(/‘/ , "'" , text) # left-single-quote entity
gsub(/’/ , "'" , text) # right-single-quote value
gsub(/’/ , "'" , text) # right-single-quote entity
gsub(/“/ , "\"" , text) # left-double-quote value
gsub(/“/ , "\"" , text) # left-double-quote entity
gsub(/”/ , "\"" , text) # right-double-quote value
gsub(/”/ , "\"" , text) # right-double-quote entity
gsub(/'/ , "'" , text) # apostrophe or tick '
gsub(/'/ , "'" , text) # apostrophe entity
gsub(/"/ , "\"" , text)
gsub(/ / , " " , text)
gsub(/</ , "<" , text)
gsub(/>/ , ">" , text)
gsub(/&/ , "\\&" , text)
return(text) ;
}
##### End html2txt #####
function txt2uri(text, uritext) # see RFC3986 (STD0066) section 2.2
{
uritext = text ;
gsub(/%/ ,"%25",uritext)
gsub(/ / ,"%20",uritext) # Removed escape (/\ /) 2025-02-27 --Bob.
gsub(/:/ ,"%3A",uritext)
gsub(/\// ,"%2F",uritext)
gsub(/\?/ ,"%3F",uritext)
gsub(/#/ ,"%23",uritext)
gsub(/\[/ ,"%5B",uritext)
gsub(/]/ ,"%5D",uritext)
gsub(/@/ ,"%40",uritext)
gsub(/!/ ,"%21",uritext)
gsub(/\$/ ,"%24",uritext)
gsub(/&/ ,"%26",uritext)
gsub(/'/ ,"%27",uritext)
gsub(/\(/ ,"%28",uritext)
gsub(/)/ ,"%29",uritext)
gsub(/\*/ ,"%2A",uritext)
gsub(/\+/ ,"%2B",uritext)
gsub(/,/ ,"%2C",uritext)
gsub(/;/ ,"%3B",uritext)
gsub(/=/ ,"%3D",uritext)
gsub(/\|/ ,"%7C",uritext) # Not part of RFC3986 (I think)
gsub(/~/ ,"%7E",uritext) # Not part of RFC3986 (I think)
gsub(/\^/ ,"%5E",uritext) # Not part of RFC3986 (I think)
gsub(/`/ ,"%60",uritext) # Not part of RFC3986 (I think)
gsub(/\{/ ,"%7B",uritext) # Not part of RFC3986 (I think)
gsub(/\}/ ,"%7D",uritext) # Not part of RFC3986 (I think)
gsub(/"/ ,"%22",uritext) # Not part of RFC3986 (I think)
gsub(/</ ,"%3C",uritext) # Not part of RFC3986 (I think)
gsub(/>/ ,"%3E",uritext) # Not part of RFC3986 (I think)
gsub(/\\/ ,"%5C",uritext) # Not part of RFC3986 (I think)
return(uritext) ;
}
##### End txt2uri #####
function uri2txt(uri, text)
{
text = uri
gsub(/%20/," " ,text)
gsub(/%3A/,":" ,text)
gsub(/%2F/,"/" ,text)
gsub(/%3F/,"?" ,text)
gsub(/%23/,"#" ,text)
gsub(/%5B/,"[" ,text)
gsub(/%5D/,"]" ,text)
gsub(/%40/,"@" ,text)
gsub(/%21/,"!" ,text)
gsub(/%24/,"$" ,text)
gsub(/%26/,"\\&" ,text) # unescaped '&' would indicate "replacement text"
gsub(/%27/,"'" ,text)
gsub(/%28/,"(" ,text)
gsub(/%29/,")" ,text)
gsub(/%2A/,"*" ,text)
gsub(/%2B/,"+" ,text)
gsub(/%2C/,"," ,text)
gsub(/%3B/,";" ,text)
gsub(/%3D/,"=" ,text)
gsub(/%7C/,"|" ,text)
gsub(/%7E/,"~" ,text)
gsub(/%5E/,"^" ,text)
gsub(/%60/,"`" ,text)
gsub(/%7B/,"{" ,text)
gsub(/%7D/,"}" ,text)
gsub(/%22/,"\"" ,text)
gsub(/%3C/,"<" ,text)
gsub(/%3E/,">" ,text)
gsub(/%0D%0A/,"\\n" ,text)
gsub(/%5C/,"\\" ,text) # not part of RFC3986 ?
gsub(/%25/,"%" ,text)
return(text)
}
function makehtml(string, numurls,i,htmlstring,path,gwiduri,outstring)
{
numurls = split(string, urls, " ") #split string into urls on space. If a URL is meant to include a space then the input string must substitute '+'
for (i=1; i<=numurls; i++)
{
if(substr(urls[i],1,2) == "\\\\")
{
path = urls[i] ;
gsub(/\\\\/,"/",path) ;
htmlstring = "<a href=\"file:///" txt2uri(path) "\">" txt2html(urls[i]) "</a>" ;
}
else if(tolower(substr(urls[i],1,5)) == "http:") # If the string starts with http: then assume it is already txt2uri converted
htmlstring = "<a href=\"http:" substr(urls[i],6) "\" title=\"" gettitle(urls[i]) "\">" txt2html(urls[i]) "</a>"
else if(tolower(substr(urls[i],1,6)) == "https:") # If the string starts with https: then assume it is already txt2uri converted
htmlstring = "<a href=\"https:" substr(urls[i],7) "\" title=\"" gettitle(urls[i]) "\">" txt2html(urls[i]) "</a>"
else if(tolower(substr(urls[i],1,2)) == "//") # If the string starts with // then assume it is already txt2uri converted
htmlstring = "<a href=\"//" substr(urls[i],3) "\" title=\"" gettitle(urls[i]) "\">" txt2html(urls[i]) "</a>"
else if(atpos=index(urls[i], "@"))
{
htmlstring = "<span class=\"vcard\"><a class=\"email\" href=\"mailto:" txt2uri(urls[i]) "\" title=\"E-mail to " txt2html(urls[i]) "\">" txt2html(urls[i]) "</a>" ; # E-mail address is not cleaned with txt2uri() so @ stays as symbol. But txt2uri() may be necessary!
# if ((substr(urls[i],atpos+1) == "sobac.com") && !NOEGUIDE)
# { gwiduri = txt2uri(smtp2gwid(substr(urls[i],1,atpos-1))) ;
# htmlstring = htmlstring " <a class=\"url\" href=\"http://eguide.sobac.com/eGuide/servlet/eGuide?Action=Detail.get&User.dn=cn%3d" gwiduri "%2cou%3dStaff%2co%3dsobac&Directory.uid=Staff\" title=\"Look up " gwiduri " in eGuide Staff Container\" target=\"ldap\">S</a>"
# htmlstring = htmlstring " <a class=\"url\" href=\"http://eguide.sobac.com/eGuide/servlet/eGuide?Action=Detail.get&User.dn=cn%3d" gwiduri "%2cou%3dNon%2dStaff%2co%3dsobac&Directory.uid=Non_Staff\" title=\"Look up " gwiduri " in eGuide Non-Staff Container\" target=\"ldap\">N</a>"
# }
htmlstring = htmlstring "</span>"
}
else if( (tolower(substr(urls[i],1,4)) == "tel:" ) || (substr(urls[i],1,1) == "+") )
{
if (tolower(substr(urls[i],1,4)) == "tel:" )
{
urls[i] = substr(urls[i],5) # strip out "tel:"
}
htmlstring = "<span class=\"vcard h-card\"><a class=\"p-tel\" href=\"tel:" urls[i] "\" title=\"Phone this number\">" urls[i] "</a></span>" ;
}
else htmlstring = txt2html(urls[i]) ;
outstring = outstring " " htmlstring # Concatenate to outstring separated by spaces
} # for i<=numurls
return(trim(outstring)) ; # Concatenation above prepends an extra space, so trim it
}
##### End makehtml #####
function parsecsv(rawfield,fieldarray, fieldnum,qflag,i,char)
{
#
# Note that parsecsv() does not currently handle embedded CRLF
# eg. "data","data<CRLF>data","data"
#
delete fieldarray ;
qflag = 0 ; # TRUE if inside field delimiters
fieldnum = 1 ;
if(!FIELD_SEPARATOR)
FIELD_SEPARATOR = "," ;
if(!FIELD_DELIMITER)
FIELD_DELIMITER = "\"" ;
for(i=1; i <= length(rawfield); i++)
{ char = substr(rawfield,i,1)
if (char == FIELD_DELIMITER)
{
if (substr(rawfield,i+1,1) == FIELD_DELIMITER) # check for two adjacent field delimiters; treat as one character
{
i++ # skip over the next character, and add the delimiter to the output string
fieldarray[fieldnum] = fieldarray[fieldnum] FIELD_DELIMITER
}
else
qflag = !qflag;
}
else if (char == FIELD_SEPARATOR)
{ if (qflag)
fieldarray[fieldnum] = fieldarray[fieldnum] FIELD_SEPARATOR ;
else
{
fieldnum++ ;
}
}
else
{ fieldarray[fieldnum] = fieldarray[fieldnum] char ;
}
}
return(fieldnum);
}
##### End of parsecsv() #####
function printcsv(field) # Determine whether to print a field with delimiters
{
if(!FIELD_SEPARATOR)
FIELD_SEPARATOR = "," ;
if(!FIELD_DELIMITER)
FIELD_DELIMITER = "\"" ;
# Double field delimiters to escape them
gsub(FIELD_DELIMITER,FIELD_DELIMITER FIELD_DELIMITER,field)
# Apply field delimiters when field separator is in field
if (field ~ FIELD_SEPARATOR)
field = FIELD_DELIMITER field FIELD_DELIMITER
return(field)
}
function valnameid(text, valtext) # Validate NAME and ID token names
{
valtext = text
gsub(/[^A-Za-z0-9\-_:\.]/, "_", valtext)
if(substr(valtext,1,1) !~ /[A-Za-z]/)
{
valtext = "a" valtext # Prepend an 'a'
}
return(valtext)
}
##### End of valnameid() #####
function printhtmlhead(title,noclosetag)
{
if (!QUOTE)
QUOTE = "\""
if ((ENVIRON["REQUEST_METHOD"]) && (!NOHTTP)) # If this is a CGI request and NOHTTP has not been specified
print("Content-type: text/html; charset=UTF-8\n") # ...then print HTTP header
### xml directives are not supported in HTML5
# print("<?xml version=" QUOTE "1.0" QUOTE " encoding=" QUOTE "utf-8" QUOTE "?>") ;
### Removed XHTML Strict --Bob 2022-05-06
# print("<!DOCTYPE html PUBLIC " QUOTE "-//W3C//DTD XHTML 1.0 Strict//EN" QUOTE ) ;
# print(" " QUOTE "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" QUOTE ">") ;
# print("<html xmlns=" QUOTE "http://www.w3.org/1999/xhtml" QUOTE ) ;
# print(" xml:lang=" QUOTE "en" QUOTE ) ;
# print(" lang=" QUOTE "en" QUOTE ">") ;
### Added HTML5 header --Bob 2022-05-06
print("<!DOCTYPE html>")
print("<html lang=\"en\">")
print(" <head>") ;
print(" <meta http-equiv=" QUOTE "Content-Type" QUOTE ) ;
print(" content=" QUOTE "text/html;charset=utf-8" QUOTE ">") ;
print(" <link rel=" QUOTE "stylesheet" QUOTE ) ;
print(" href=" QUOTE "/default.css" QUOTE ) ;
print(" type=" QUOTE "text/css" QUOTE ">") ;
for(i in LINKREL)
print(" " LINKREL[i])
print(" <meta name=" QUOTE "generator" QUOTE ) ;
print(" content=" QUOTE "AWK tools for HTML by Bob Jonkman" QUOTE ">") ;
print(" <meta name=" QUOTE "author" QUOTE ) ;
print(" content=" QUOTE "Bob Jonkman bjonkman@sobac.com;" QUOTE ">") ;
print(" <meta name=" QUOTE "description" QUOTE ) ;
print(" content=" QUOTE "Datetime: " strftime("%FT%T") )
print(" " txt2html(title) QUOTE ">" )
print(" <title>" txt2html(title) "</title>") ;
if ("" == noclosetag) { # Suppress close tag if requested
print (" </head>")
}
}
##### End of printhtmlhead() #####
function parsecgi(inputstring,outputarray, querystring,paramstring,numparam)
{ delete querystring
delete outputarray
numparam = split(inputstring,querystring,"&")
for(i in querystring)
{
gsub(/\+/," ",querystring[i]) # remove + as space substitute
split(querystring[i],paramstring,"=")
# allow multi-value parameters, separate with FS
if (outputarray[uri2txt(paramstring[1])])
{
outputarray[uri2txt(paramstring[1])] = outputarray[uri2txt(paramstring[1])] FS uri2txt(paramstring[2]) ;
numparam-- ;
}
else
outputarray[uri2txt(paramstring[1])] = uri2txt(paramstring[2]) ;
}
return(numparam)
}
##### End of parsecgi() #####
function txt2gwapi(text)
{
gsub(/;/ ,"\\;" , text) # escape semicolon to backslash semicolon
gsub(/"/ , "\\\"", text) # escape unpaired double quote to backslash unpaired double quote
return(text)
}
##### End of txt2gwapi()
# Results of getheaders("Alpha,Beta,Gamma",namearray,numberarray)
# namearray["Alpha"] == 1
# namearray["Beta"] == 2
# namearray["Gamma"] == 3
# numberarray[1] == "Alpha"
# numberarray[2] == "Beta"
# numberarray[3] == "Gamma"
function getheaders(instring,namearray,numberarray, numfields,i) {
numfields = parsecsv(instring,numberarray)
for(i=1; i<=numfields; i++)
namearray[numberarray[i]] = i ;
return(numfields)
}
##### End of getheaders()
function printarray(arrayname, name, i)
{
if (isarray(arrayname)) {
print "Array:"
for (name in arrayname) {
if (isarray(arrayname[name])) {
print "\nBegin Subarray"
printarray(arrayname[name])
print "End Subarray\n"
} else {
print i++ " name=" name " arrayname[name]=" arrayname[name]
} # if(isarray(arrayname[name]))
} # for (name in arrayname)
} else { # not an array
print "Scalar: " arrayname
} # if(isarray(arrayname))
return i
}
##### End of printarray()
function gettitle(url)
{ return(txt2html(url)) # It's just a stub for now
}
function stripcomments(string,delimstart,delimend)
{
return gensub("\\" delimstart ".*" "\\" delimend,"","g",string) # Text to replace (escaped delimiters, eg "\\(" or "\\["), Replacement text, Global, String with text
}
# EOF: LIBRARY.AWK
← ↑ → makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from makefile
# Program : makefile
# Purpose : To re-create the files
# Author : Bob Jonkman bjonkman@sobac.com
# Date : 5 August 2022
# Modified : 5 March 2025 - Added index.html, ./makeindex
VPATH = /home/bjonkman/bin/awk/
AWKPROGS = csv2html.awk code2html.awk library.awk
CANDIDATEPROGS = csv2candidates.awk csv2municipalcandidates.awk csv2candidates-search.awk candidates.css
.PHONY : all
all : $(CANDIDATEPROGS) \
csv2municipalcandidates.html \
csv2candidates.html \
index \
rsync
csv2municipalcandidates.awk : csv2municipalcandidates-DEBUG.awk
grep -v "#####DEBUG#####" csv2municipalcandidates-DEBUG.awk > csv2municipalcandidates.awk
csv2candidates.awk : csv2candidates-DEBUG.awk
grep -v "#####DEBUG#####" csv2candidates-DEBUG.awk > csv2candidates.awk
csv2candidates-search.awk : csv2candidates-search-DEBUG.awk
grep -v "#####DEBUG#####" csv2candidates-search-DEBUG.awk > csv2candidates-search.awk
csv2candidates.html : $(AWKPROGS) $(CANDIDATEPROGS) makefile */makefile
/home/bjonkman/bin/awk/code2html.awk -v LINK=1 -v TITLE="CSV to Candidates" \
$(CANDIDATEPROGS) \
/home/bjonkman/bin/awk/csv2html.awk \
/home/bjonkman/bin/awk/code2html.awk \
/home/bjonkman/bin/awk/library.awk \
makefile \
*/makefile > csv2candidates.html
### Everything
.PHONY : index
index : makeindex FORCE
./makeindex
.PHONY : rsync
rsync : FORCE
rsync -av --no-recursive --dirs --delete * irving:/home/jonkman/public_html/Poliblog-Elections/
FORCE:
# EOF: makefile
← ↑ → 2019-10-29-Federal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2019-10-29-Federal-Election/makefile
# File : makefile
# Purpose : Update 2019 Federal election candidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 20 November 2023
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
# candidates.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2019-10-29-Federal-Election/candidates.ods
# cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2019-10-29-Federal-Election/candidates.ods
index.html : candidates.csv ../csv2candidates.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
# Parties-Ridings.html : Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv
Parties-Ridings.html : Parties.csv Ridings.csv
# /home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv > Parties-Ridings.html
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv Ridings.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2019-10-29-Federal-Election/
rsync -av --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2021-09-20-Federal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2021-09-20-Federal-Election/makefile
# File : makefile
# Purpose : Update 2021 Federal election candidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 20 November 2023
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
index.html : candidates.csv ../csv2candidates.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
# Parties-Ridings.html : Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv
Parties-Ridings.html : Parties.csv
# /home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv > Parties-Ridings.html
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2021-09-20-Federal-Election/
rsync -av --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2022-06-02-Provincial-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2022-06-02-Provincial-Election/makefile
# File : makefile
# Purpose : Update 2022 Provincial election candidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 20 November 2023
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
# candidates.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2022-06-02-Provincial-Election/candidates.ods
# cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2022-06-02-Provincial-Election/candidates.ods
index.html : candidates.csv ../csv2candidates.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
# Parties-Ridings.html : Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv
Parties-Ridings.html : Parties.csv Ridings.csv
# /home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv Ridings.csv ReservedParties.csv UnregisteredParties.csv > Parties-Ridings.html
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 Parties.csv Ridings.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2022-06-02-Provincial-Election/
rsync -av --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2022-10-24-Municipal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2022-10-24-Municipal-Election/makefile
# File : makefile
# Purpose : csv2municipalcandidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 30 July 2022
.PHONY : all
all : 2022-Municipal.ods 2022-Municipal.csv index.html Municipality.html searchassistant.html rsync
2022-Municipal.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.ods
cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.ods /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/
index.html : /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.csv > /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/index.html
2022-Municipal.csv : /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.ods
soffice --convert-to "csv" /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.ods
Municipality.html : /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/Municipality.csv
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Municipalities, URLs and Logos" /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/Municipality.csv > /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/Municipality.html
searchassistant.html : /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/2022-Municipal.csv > /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/2022-10-24-Municipal-Election/ irving:/home/jonkman/public_html/Poliblog-Elections/2022-10-24-Municipal-Election/
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/images/ irving://home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2023-11-13-Cambridge-Ward01-Byelection/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2023-11-13-Cambridge-Ward01-Byelection/makefile
# File : makefile
# Purpose : csv2municipalcandidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 30 July 2022
# Modified: 1 November 2023
.PHONY : all
all : candidates.ods candidates.csv index.html Municipalities.html searchassistant.html rsync
index.html : /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.csv > /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/index.html
candidates.csv : /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.ods
soffice --convert-to "csv" /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.ods
Municipalities.html : /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/Municipalities.csv
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Municipalities, URLs and Logos" /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/Municipalities.csv > /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/Municipalities.html
searchassistant.html : /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/candidates.csv > /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/2023-11-13-Cambridge-Ward01-Byelection/ irving:/home/jonkman/public_html/Poliblog-Elections/2023-11-13-Cambridge-Ward01-Byelection/
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/images/ irving://home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2025-02-27-Provincial-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2025-02-27-Provincial-Election/makefile
# File : makefile
# Purpose : Update 2025 Ontario Provincial Election candidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : based on 30 October 2023
# Modified : 2 Feb 2025 - Added library.awk and csv2html.awk as dependencies
# - Add 'rm -v *.tmp'
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
# candidates.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2023-Provincial-Byelection-Kitchener/candidates.ods
# cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2023-Provincial-Byelection-Kitchener/candidates.ods
index.html : candidates.csv ../csv2candidates.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
Parties-Ridings.html : *.csv /home/bjonkman/bin/awk/csv2html.awk /home/bjonkman/bin/awk/library.awk
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 *.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
# rm -v *.tmp
rsync -av --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2025-02-27-Provincial-Election/
rsync -av --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2025-Federal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2025-Federal-Election/makefile
# File : makefile
# Purpose : Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 23 November 2023
# Notes : for upcoming Federal Election, change folder name when date is known
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
# candidates.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2025-Federal-Election/candidates.ods
# cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2025-Federal-Election/candidates.ods
index.html : candidates.csv ../csv2candidates.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
Parties-Ridings.html : *.csv /home/bjonkman/bin/awk/csv2html.awk /home/bjonkman/bin/awk/library.awk
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 *.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2025-Federal-Election/
rsync -av --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2026-10-26-Municipal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2026-10-26-Municipal-Election/makefile
# File : makefile
# Purpose : csv2municipalcandidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 10 May 2026
.PHONY : all
all : Municipal.ods Municipal.csv index.html Municipality.html searchassistant.html rsync
## Not copying to/from Nextcloud at moment, maybe if CiviTechWR asks
### Municipal.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.ods
### cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.ods /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/
index.html : /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates.awk /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.csv > /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/index.html
Municipal.csv : /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.ods
mv Municipal.csv Municipal-previous.csv
soffice --convert-to "csv" /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.ods
Municipality.html : /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipality.csv
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Municipalities, URLs and Logos" /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipality.csv > /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipality.html
searchassistant.html : /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search.awk /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/Municipal.csv > /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/searchassistant.html
.PHONY : rsync
rsync :
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/2026-10-26-Municipal-Election/ irving:/home/jonkman/public_html/Poliblog-Elections/2026-10-26-Municipal-Election/
rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/images/ irving://home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → 2029-Federal-Election/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from 2029-Federal-Election/makefile
# File : makefile
# Purpose : Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 23 November 2023
# Notes : for upcoming Federal Election, change folder name when date is known
.PHONY : all
all : candidates.ods candidates.csv index.html Parties-Ridings.html searchassistant.html rsync
# candidates.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/$(basename $(pwd))/candidates.ods
# cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/$(basename $(pwd))2025-Federal-Election/candidates.ods
index.html : candidates.csv ../csv2candidates.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates.awk candidates.csv > index.html
candidates.csv : candidates.ods
soffice --convert-to "csv" candidates.ods
Parties-Ridings.html : *.csv /home/bjonkman/bin/awk/csv2html.awk /home/bjonkman/bin/awk/library.awk
/home/bjonkman/bin/awk/csv2html.awk -v TITLE="Parties and Ridings" -v LINK=1 -v SORTABLE=1 *.csv > Parties-Ridings.html
searchassistant.html : candidates.csv ../csv2candidates-search.awk /home/bjonkman/bin/awk/library.awk
../csv2candidates-search.awk candidates.csv > searchassistant.html
.PHONY : rsync
rsync :
rsync -aP --delete ./ irving:/home/jonkman/public_html/Poliblog-Elections/2029-Federal-Election/
rsync -aP --delete ../images/ irving:/home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
← ↑ → MunicipalTestData/makefile
Generated on Thu, 16 Jul 2026 15:29:19 -0400 from MunicipalTestData/makefile
# File : MunicipalTestData-makefile
# Purpose : csv2municipalcandidates - Re-run various scripts when updates are made
# Author : Bob Jonkman
# Date : 16 June 2026
# Note : This makefile acts on files in the current folder.
.PHONY : all
### Not rebuilding all these files for test data
#### all : MunicipalTestData.ods MunicipalTestData.csv MunicipalTestData.html Municipality.html MunicipalTestData-searchassistant.html rsync
all : MunicipalTestData.ods MunicipalTestData.csv MunicipalTestData.html MunicipalTestData-searchassistant.html
## Not copying to/from Nextcloud at moment, maybe if CiviTechWR asks
### MunicipalTestData.ods : /home/bjonkman/mount/nextcloud/Documents/Websites/MunicipalTestData/MunicipalTestData.ods
### cp -pv /home/bjonkman/mount/nextcloud/Documents/Websites/PoliBlog/MunicipalTestData/MunicipalTestData.ods ./
MunicipalTestData.html : MunicipalTestData.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates-DEBUG.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2municipalcandidates-DEBUG.awk MunicipalTestData.csv > MunicipalTestData.html
MunicipalTestData.csv : MunicipalTestData.ods
mv MunicipalTestData.csv MunicipalTestData-previous.csv
soffice --convert-to "csv" MunicipalTestData.ods
### Not for test data
#### Municipality.html : ./Municipality.csv
#### /home/bjonkman/bin/awk/csv2html-DEBUG.awk -v TITLE="Municipalities, URLs and Logos" /home/bjonkman/Documents/Websites/MunicipalTestData/Municipality.csv > /home/bjonkman/Documents/Websites/MunicipalTestData/Municipality.html
MunicipalTestData-searchassistant.html : MunicipalTestData.csv /home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search-DEBUG.awk
/home/bjonkman/Documents/Websites/PoliBlog/csv2candidates-search-DEBUG.awk MunicipalTestData.csv > MunicipalTestData-searchassistant.html
### Not for test data
#### .PHONY : rsync
#### rsync :
#### rsync -av --delete /home/bjonkman/Documents/Websites/MunicipalTestData/ irving:/home/jonkman/public_html/Poliblog-Elections/MunicipalTestData/
#### rsync -av --delete /home/bjonkman/Documents/Websites/PoliBlog/images/ irving://home/jonkman/public_html/Poliblog-Elections/images/
# EOF: makefile
18 files processed.