diff --git a/shell-scripts.d/canticle-to-xml.sh b/shell-scripts.d/canticle-to-xml.sh
new file mode 100755
index 0000000..1b4ad1f
--- /dev/null
+++ b/shell-scripts.d/canticle-to-xml.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# A script which converts plaintext cantos from one canticle to XML formatting.
+# Must be run inside canticle directory: [inferno,purgatorio,paradiso]-xml.d
+# Tag scripts must be in the above directory.
+
+# 1) word-tags.sh --- add tags to each word.
+# 2) terzina-tags.sh --- add tags to each terzina
+# 3) top-level-tags.sh --- add XML header; add and tags
+
+# Check parent directory
+if [[ "$(pwd | awk -F'/' '{print $NF}')" =~ inferno-xml.d|purgatorio-xml.d|paradiso-xml.d ]]; then
+ clear
+ echo "Starting..."
+ cp ../shell-scripts.d/word-tags.sh . && ./word-tags.sh && rm word-tags.sh
+ cp ../shell-scripts.d/terzina-tags.sh . && ./terzina-tags.sh && rm terzina-tags.sh
+ cp ../shell-scripts.d/top-level-tags.sh . && ./top-level-tags.sh && rm top-level-tags.sh
+fi
diff --git a/shell-scripts.d/terzina-tags.sh b/shell-scripts.d/terzina-tags.sh
new file mode 100755
index 0000000..b025485
--- /dev/null
+++ b/shell-scripts.d/terzina-tags.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Add terzina tags to each canto.xml file in a directory
+# Also add canto number attribute.
+
+echo -e "\nAdding terzina tags and numbers."
+
+for file in *;
+do
+ if [[ "$file" != 'terzina-tags.sh' ]]; then # exclude this script
+ if [[ "$file" != sed* ]]; then # exclude sed file made later
+ echo -e "Modifying $file"
+ # Every third line (mod 3) add a closing tag, newline, and opening tag.
+ awk -i inplace '{print}; NR % 3 == 0 {print "\t\t\n\t\t"}' "$file"
+ # Add an opening tag at the beginning and a closing tag at the end because the above awk doesn't.
+ sed -i '1s/^/\t\t\n/' "$file"
+ echo -e "\t\t" >> "$file"
+ # Add the canto number attribute (at most 160/3 ~ 54 terzine)
+ for i in $(seq 1 54);
+ do
+ # Substitute in the actual number with sed. I couldn't figure out
+ # how to include the $i variable with the surrounding attribute
+ # quotes in the awk statement, so I just used sed here separately.
+
+ # We start at i=2 because the awk command above
+ # doesn't add a terzina tag to the first terzina.
+ TERZ_NUM=$((i + 1))
+ # Replace only first occurence of ~NUM~.
+ sed -i -e "0,/~NUM~/ s/~NUM~/\"$TERZ_NUM\"/" "$file"
+ done
+ fi
+ fi
+done
diff --git a/shell-scripts.d/top-level-tags.sh b/shell-scripts.d/top-level-tags.sh
new file mode 100755
index 0000000..ec24e42
--- /dev/null
+++ b/shell-scripts.d/top-level-tags.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# This script adds top-level tags to your XML files.
+# - XML header
+# - and tags with attributes
+
+echo -e "\nAdding top-level tags."
+
+for file in *;
+do
+ if [[ "$file" != 'top-level-tags.sh' ]]; then
+ CANTO_NUM="$(echo "'$file'" | tr -d -c 0-9)" # get canto number
+ if [[ "$file" == inferno* ]]; then
+ COMM_NUM="$CANTO_NUM" # same as inferno canto number
+ elif [[ "$file" == purgatorio* ]]; then
+ COMM_NUM="$((CANTO_NUM + 34))" # add 34 if in purgatory
+ else
+ COMM_NUM="$((CANTO_NUM + 67))" # add 68 if in paradise
+ fi
+ # Add tags to first line of file
+ sed -i '1s/^/\n\n\n\t\n/' "$file"
+ # Replace canto number and comedy number
+ sed -i "s/~CANTO_NUM~/\"$CANTO_NUM\"/" "$file"
+ sed -i "s/~COMM_NUM~/\"$COMM_NUM\"/" "$file"
+ # Add tags to end of file
+ echo -e "\t" >> "$file"
+ echo -e "" >> "$file"
+ # Add a `.xml` extension
+ mv "$file" "$file.xml"
+ fi
+done
diff --git a/shell-scripts.d/word-tags.sh b/shell-scripts.d/word-tags.sh
new file mode 100755
index 0000000..97f0186
--- /dev/null
+++ b/shell-scripts.d/word-tags.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# For all files in directory:
+# - replace all spaces with ` `
+# - add `` to the start of each line
+# - add `` to the end of each line
+# Also add proper indentation.
+# This script treats punctuation as words.
+
+echo -e "\nAdding word tags."
+
+for file in *;
+do
+ if [[ "$file" != 'word-tags.sh' ]]; then
+ sed -i 's/\ /<\/word>\ /g' "$file"
+ sed -i 's/^/\t\t\t/' "$file"
+ sed -i 's/$/<\/word>/' "$file"
+ fi
+done
diff --git a/shell-scripts.d/xpath.sh b/shell-scripts.d/xpath.sh
new file mode 100755
index 0000000..a0c644d
--- /dev/null
+++ b/shell-scripts.d/xpath.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+# An interactive xpath query tool
+
+touch .prev-xpath.tmp
+
+GREEN='\033[0;32m'
+NC='\033[0m' # no color
+
+clear
+read -p "Choose a canto: [i,r,p][1-34]" CANTO
+CANTICLE="$(echo $CANTO | tr -d -c a-z)"
+CANTO_NUM="$(echo $CANTO | tr -d -c 0-9)"
+
+file="-${CANTO_NUM}.xml"
+
+if [[ "$CANTICLE" == 'i' ]]; then
+ CANTICLE='Inferno'
+ file="inferno${file}"
+elif [[ "$CANTICLE" == 'r' ]]; then
+ CANTICLE='Purgatorio'
+ file="purgatorio${file}"
+elif [[ "$CANTICLE" == 'p' ]]; then
+ CANTICLE='Paradiso'
+ file="paradiso${file}"
+else
+ echo "Not a valid canto file: $file"
+fi
+
+if [[ -e "$file" ]]; then
+ clear && echo -e "You are searching ${CANTICLE^} ${CANTO_NUM}.\nAdd a trailing slash ${GREEN}\`/\`${NC} to your search before submitting it. The default first two search choices are ${GREEN}\`canto\`${NC} and then ${GREEN}\`terzine\`${NC}. Press ${GREEN}${NC} for those defaults and type something else to search that.\n"
+ i='0'
+ read -p "Load previous: (Y)/n " PREV
+ case "$PREV" in
+ N|n)
+ search=''
+ ;;
+ Y|y|*)
+ search="$(cat .prev-xpath.tmp)"
+ ;;
+ esac
+ while true; do
+ read -e -i "$search" -p "$(echo -e "\nSearch: \n "${GREEN}"===> "${NC})" nsearch
+ if [[ "${nsearch: -1}" == '/' ]]; then # if last char trailing slash then search
+ path="${nsearch::-1}"
+ echo "$path" > .prev-xpath.tmp # remove trailing slash
+ xmllint --xpath "$path" $file | batcat -l 'xml'
+ exit
+ elif [[ "$i" == '0' ]] && [[ "$nsearch" == '' ]]; then
+ search="/canto"
+ elif [[ "$nsearch" == '/canto' ]]; then
+ search="/canto/terzine"
+ else
+ search="${nsearch}"
+ fi
+ i=$[$i+1]
+ done
+else
+ echo "Canto file not found. What directory are you in?"
+fi