Created
July 15, 2025 10:35
-
-
Save ajesler/777becf1fd7cd3c142d47445082fbfdb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // References | |
| // https://developers.google.com/apps-script/reference/spreadsheet/ | |
| // https://developers.google.com/apps-script/reference/xml-service | |
| // https://github.com/international-orienteering-federation/datastandard-v3/blob/master/IOF.xsd | |
| // https://github.com/international-orienteering-federation/datastandard-v3/blob/master/examples/CourseData_Individual_Step2.xml | |
| // https://condes.net/onlinehelp/?HIDD_COPYOBJECTS.html | |
| // See https://docs.google.com/document/d/1qSCO3uMm6tzxSS41zrhS6jWHufgTpipzf4SsAsMLLXQ for instructions on how to use this | |
| // Convert a spreadsheet of control information into a IOF formatted XML file of controls that can be imported into Condes orienteering software. | |
| function exportAsIOF() { | |
| // Update the values in this section to reflect your setup | |
| // -- BEGIN CHANGE THESE VALUES -- | |
| const creator = "Your Name"; | |
| const eventName = "My Rogaine"; | |
| // This should be the full range of cells your sheet uses | |
| const sheetCellRange = 'A1:M53'; | |
| // Get this from the last part of the Google Drive folder URL | |
| // eg https://drive.google.com/drive/u/0/folders/1FeABCaKlhXZYZuoX3i7WIROxEc8588pC | |
| const folderId = "1FeABCaKlhXZYZuoX3i7WIROxEc8588pC"; | |
| const fileName = "controls.xml"; | |
| // Set these to be the column number in the spreadsheet you want to pull the id, lat, and lng from. | |
| const controlIdColNumber = 3; | |
| const latColNumber = 6; | |
| const lngColNumber = 7; | |
| // -- END CHANGE THESE VALUES -- | |
| var sheet = SpreadsheetApp.getActiveSheet(); | |
| var rows = sheet.getRange(sheetCellRange).getValues(); | |
| rows.shift(); // Get rid of the header rows, we don't need them for this. | |
| const createdAtTimestamp = Utilities.formatDate( | |
| new Date(), | |
| Session.getScriptTimeZone(), | |
| "yyyy-MM-dd HH:mm:ss" | |
| ); | |
| const ns = XmlService.getNamespace("http://www.orienteering.org/datastandard/3.0"); | |
| let root = XmlService.createElement('CourseData', ns) | |
| .setAttribute("iofVersion", "3.0") | |
| .setAttribute("Creator", creator) | |
| .setAttribute("createTime", createdAtTimestamp); | |
| let event = XmlService.createElement("Event", ns); | |
| event.addContent(XmlService.createElement("Name", ns).setText(eventName)) | |
| root.addContent(event); | |
| let raceCourseData = XmlService.createElement("RaceCourseData", ns); | |
| root.addContent(raceCourseData); | |
| rows.forEach((c) => { | |
| // If we are missing any info for the control, skip it. | |
| if(c[controlIdColNumber] == "" || c[latColNumber] == "" || c[lngColNumber] == "") { | |
| return; | |
| } | |
| let control = XmlService.createElement("Control", ns); | |
| control.addContent(XmlService.createElement("Id", ns).setText(c[2])); | |
| let position = XmlService.createElement("Position", ns) | |
| .setAttribute("lat", c[latColNumber]) | |
| .setAttribute("lng", c[lngColNumber]); | |
| control.addContent(position); | |
| // This isn't used, but if this element isn't here, condes does not import the control. | |
| let mapPosition = XmlService.createElement("MapPosition", ns) | |
| .setAttribute("unit", "mm") | |
| .setAttribute("x", "1") | |
| .setAttribute("y", "1"); | |
| control.addContent(mapPosition); | |
| raceCourseData.addContent(control); | |
| }) | |
| let document = XmlService.createDocument(root); | |
| let xmlContent = XmlService.getPrettyFormat().format(document); | |
| // console.log(xml); // Uncomment if you want to see the generated XML | |
| let file = getFileWithName(folderId, fileName); | |
| if(file){ | |
| // File exists, so replace the contents | |
| file.setContent(xmlContent); | |
| } else { | |
| // File doesn't exist, so create it | |
| DriveApp.getFolderById(folderId).createFile(fileName, xmlContent); | |
| } | |
| } | |
| function getFileWithName(folderId, fileName) { | |
| var folder = DriveApp.getFolderById(folderId); | |
| var files = folder.getFilesByName(fileName); | |
| while (files.hasNext()) { | |
| var file = files.next(); | |
| return file; | |
| } | |
| return null; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment