Repository: Bridgeconn/usfm-grammar (language: python)

Issue:
USX export
Convert the AST representation of USFM directly to the USX(xml) format.




Discussion / hints:

Fix patch:
diff --git a/python-usfm-parser/API guide for python usfm_grammar.ipynb b/python-usfm-parser/API guide for python usfm_grammar.ipynb
index ea28ee88..5a643496 100644
--- a/python-usfm-parser/API guide for python usfm_grammar.ipynb
+++ b/python-usfm-parser/API guide for python usfm_grammar.ipynb
@@ -23,7 +23,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 2,
    "id": "b3d034a2",
    "metadata": {},
    "outputs": [],
@@ -200,11 +200,224 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "818e36d9",
+   "id": "38a6d3fb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import xml.etree.ElementTree as ET\n",
+    "\n",
+    "usx_elem = my_parser.toUSX()\n",
+    "usx_str = ET.tostring(usx_elem, encoding=\"unicode\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6359c463",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "usx_str"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "295dae47",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!pip install lxml"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "583efddc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!pip install rnc2rng"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "2bd40ba2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'<usx version=\"3.0\"><book code=\"GEN\" style=\"id\" /><chapter number=\"1\" style=\"c\" sid=\"GEN 1\" /><para style=\"p\" /><chapter eid=\"GEN 1\" /></usx>'"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import sys\n",
+    "sys.path.append('/home/kavitha/Documents/PEG JS and USFM/usfm-grammar-v3/usfm-grammar/python-usfm-parser/ENV/lib/python3.8/site-packages')\n",
+    "\n",
+    "\n",
+    "from usfm_grammar import USFMParser, Filter\n",
+    "import xml.etree.ElementTree as ET\n",
+    "\n",
+    "input_usfm_str = open(\"origin.usfm\",\"r\", encoding='utf8').read()\n",
+    "my_parser = USFMParser(input_usfm_str)\n",
+    "\n",
+    "usx_elem = my_parser.toUSX()\n",
+    "usx_str = ET.tostring(usx_elem, encoding=\"unicode\")\n",
+    "\n",
+    "usx_str"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "id": "a680a0b6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from lxml import etree\n",
+    "with open(\"../schemas/usx.rnc\") as f:\n",
+    "    usxrnc_doc  = f.read()\n",
+    "    relaxng = etree.RelaxNG.from_rnc_string(usxrnc_doc)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "id": "0fac8a56",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "valid\n"
+     ]
+    }
+   ],
+   "source": [
+    "\n",
+    "\n",
+    "from io import StringIO \n",
+    "usx_f = StringIO(usx_str)\n",
+    "doc = etree.parse(usx_f)\n",
+    "if relaxng.validate(doc):\n",
+    "    print(\"valid\")\n",
+    "else:\n",
+    "    relaxng.assertValid(doc)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d4e0c784",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "id": "1ea6bb28",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "valid\n"
+     ]
+    }
+   ],
+   "source": [
+    "origin_usx_str = '''<usx version=\"3.0\">\n",
+    "  <book code=\"GEN\" style=\"id\" />\n",
+    "  <para style=\"mt1\">MARK</para>\n",
+    "  <chapter number=\"1\" style=\"c\" sid=\"GEN 1\" />\n",
+    "  <para style=\"p\">\n",
+    "    <verse number=\"1\" style=\"v\" sid=\"GEN 1:1\" />\n",
+    "    verse one \n",
+    "    <verse eid=\"GEN 1:1\" />\n",
+    "    <verse number=\"2\" style=\"v\" sid=\"GEN 1:2\" />\n",
+    "    verse two\n",
+    "    <verse eid=\"GEN 1:2\" />\n",
+    "  </para>\n",
+    "  <chapter eid=\"GEN 1\" />\n",
+    "</usx>'''\n",
+    "usx_f = StringIO(origin_usx_str)\n",
+    "doc = etree.parse(usx_f)\n",
+    "if relaxng.validate(doc):\n",
+    "    print(\"valid\")\n",
+    "else:\n",
+    "    relaxng.assertValid(doc)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "dc42b6af",
    "metadata": {},
    "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "id": "8d12593b",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "valid\n"
+     ]
+    }
+   ],
+   "source": [
+    "empty_usx_str = '''<usx version=\"3.0.0\">\n",
+    "  <book code=\"GEN\" style=\"id\" />\n",
+    "  <chapter number=\"1\" style=\"c\" sid=\"GEN 1\" />\n",
+    "  <para style=\"p\">\n",
+    "    <verse number=\"1\" style=\"v\" altnumber=\"2\" pubnumber=\"B\" sid=\"GEN 1:22\" />\n",
+    "    verse one\n",
+    "  </para>\n",
+    "  <chapter eid=\"GEN 1\" />\n",
+    "\n",
+    "</usx>'''\n",
+    "usx_f = StringIO(empty_usx_str)\n",
+    "doc = etree.parse(usx_f)\n",
+    "if relaxng.validate(doc):\n",
+    "    print(\"valid\")\n",
+    "else:\n",
+    "    relaxng.assertValid(doc)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "818e36d9",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'(File (book (id (bookcode) (description))) (mtBlock (mt (numberedLevelMax4) (text))) (chapter (c (chapterNumber)) (paragraph (p))))'"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
    "source": [
-    "my_parser.AST"
+    "my_parser.toAST()"
    ]
   },
   {
diff --git a/python-usfm-parser/sample.usfm b/python-usfm-parser/sample.usfm
index 27a50597..0154031f 100644
--- a/python-usfm-parser/sample.usfm
+++ b/python-usfm-parser/sample.usfm
@@ -15,6 +15,12 @@
 \li1 ബെന്യാമീൻ
 \p
 \v 4 ദാൻ, നഫ്താലി, ഗാദ്, ആശേർ.
+\v 12-83 They presented their offerings in the following order:
+\tr \th1 Day \th2 Tribe \th3 Leader
+\tr \tcr1 1st \tc2 Judah \tc3 Nahshon son of Amminadab
+\tr \tcr1 2nd \tc2 Issachar \tc3 Nethanel son of Zuar
+\tr \tcr1 3rd \tc2 Zebulun \tc3 Eliab son of Helon
+\p
 \v 5 യാക്കോബിന്റെ സന്താനപരമ്പരകൾ എല്ലാം കൂടി എഴുപതു പേർ ആയിരുന്നു; യോസേഫ് മുമ്പെ തന്നെ ഈജിപ്റ്റിൽ ആയിരുന്നു. \w gracious|grace\w* and then a few words later \w gracious|lemma="grace" x-myattr="metadata"\w*
 \c 2
 \s1 A Prayer of Habakkuk
@@ -34,3 +40,18 @@ word for “living,” which is rendered in this context as “human beings.”\
 was the mother of all human beings.
 \v 21 And the \nd Lord\nd* God made clothes out of animal skins for Adam and his wife,
 and he clothed them.
+\qt-s |sid="qt_123" who="Pilate"\*“Are you the king of the Jews?”\qt-e |eid="qt_123"\*
+\esb \cat History\cat*
+\ms Fish and Fishing
+\p In Jesus' time, fishing took place mostly on lake Galilee, because Jewish people
+could not use many of the harbors along the coast of the Mediterranean Sea, since these
+harbors were often controlled by unfriendly neighbors. The most common fish in the Lake
+of Galilee were carp and catfish. \wj The Law of Moses \wj* allowed people to eat any fish with
+fins and scales, but since catfish lack scales (as do eels and sharks) they were not to
+be eaten (\xt Lev 11.9-12\xt*). Fish were also probably brought from Tyre and Sidon,
+where they were dried and salted.
+...
+\p Among early Christians, the fish was a favorite image for Jesus, because the Greek
+word for fish ( \tl ichthus\tl* ) consists of the first letters of the Greek words that
+tell who Jesus is: \fig Christian Fish Image\fig*
+\esbe
diff --git a/python-usfm-parser/usfm_grammar.py b/python-usfm-parser/usfm_grammar.py
index 03f674d9..9c3dc1db 100644
--- a/python-usfm-parser/usfm_grammar.py
+++ b/python-usfm-parser/usfm_grammar.py
@@ -2,7 +2,8 @@
 import json
 from enum import Enum
 from tree_sitter import Language, Parser
-
+import xml.etree.ElementTree as ET
+from xml.dom import minidom
 
 class Filter(str, Enum):
 	ALL = "all"
@@ -22,6 +23,217 @@ class Format(str, Enum):
 parser = Parser()
 parser.set_language(USFM_LANGUAGE)
 
+PARA_STYLE_MARKERS = ["h", "toc", "toca" #identification 
+					"imt", "is", "ip", "ipi", "im", "imi", "ipq", "imq", "ipr", "iq", "ib",
+					"ili", "iot", "io", "iex", "imte", "ie", # intro
+					"mt", "mte", "cl", "cd", "ms", "mr", "s", "sr", "r", "d", "sp", "sd", #titles
+					"q", "qr", "qc", "qa", "qm", "qd", #poetry
+					"lh", "li", "lf", "lim", "litl" #lists
+					]
+
+NOTE_MARKERS = ["f", "fe", "ef", "efe", "x", "ex"]
+CHAR_STYLE_MARKERS = [ "add", "bk", "dc", "ior", "iqt", "k", "litl", "nd", "ord", "pn",
+					"png", "qac", "qs", "qt", "rq", "sig", "sls", "tl", "wj", # Special-text
+					"em", "bd", "bdit", "it", "no", "sc", "sup", # character styling
+					 "rb", "pro", "w", "wh", "wa", "wg", #special-features
+					 "lik", "liv", #structred list entries
+					 "jmp",
+					 "fr", "ft", "fk", "fq", "fqa", "fl", "fw", "fp", "fv", "fdc", #footnote-content
+					 "xo", "xop", "xt", "xta", "xk", "xq", "xot", "xnt", "xdc" #crossref-content
+					 ]
+NESTED_CHAR_STYLE_MARKERS = [item+"Nested" for item in CHAR_STYLE_MARKERS]
+DEFAULT_ATTRIB_MAP = {"w":"lemma", "rb":"gloss", "xt":"link-href", "fig":"alt"}
+TABLE_CELL_MARKERS = ["tc", "th", "tcr", "thr"]
+
+def node_2_usx(node, usfm_bytes, parent_xml_node, xml_root_node):
+	'''check each node and based on the type convert to corresponding xml element'''
+	# print("working with node: ", node, "\n")
+	if node.type == "id":
+		id_captures = USFM_LANGUAGE.query('''(id (bookcode) @book-code
+													(description) @desc)''').captures(node)
+		code = None 
+		desc = None 
+		for tupl in id_captures:
+			if tupl[1] == "book-code":
+				code = usfm_bytes[tupl[0].start_byte:tupl[0].end_byte].decode('utf-8')
+			elif tupl[1] == 'desc':
+				desc = usfm_bytes[tupl[0].start_byte:tupl[0].end_byte].decode('utf-8')
+		book_xml_node = ET.SubElement(parent_xml_node, "book")
+		book_xml_node.set("code", code)
+		book_xml_node.set("style", "id")
+		if desc is not None and desc.strip() != "":
+			book_xml_node.text = desc.strip()
+	elif node.type == "chapter":
+		chap_cap = USFM_LANGUAGE.query('''(c (chapterNumber) @chap-num)''').captures(node)[0]
+		chap_num = usfm_bytes[chap_cap[0].start_byte:chap_cap[0].end_byte].decode('utf-8')
+		ref = parent_xml_node.find("book").attrib['code']+" "+chap_num
+		chap_xml_node = ET.SubElement(parent_xml_node, "chapter")
+		chap_xml_node.set("number", chap_num)
+		chap_xml_node.set("style", "c")
+		chap_xml_node.set("sid", ref)
+		for child in node.children[1:]:
+			node_2_usx(child, usfm_bytes, parent_xml_node, xml_root_node)
+
+		prev_verses = xml_root_node.findall(".//verse")
+		if len(prev_verses)>0:
+			if "sid" in prev_verses[-1].attrib:
+				v_end_xml_node = ET.SubElement(parent_xml_node, "verse")
+				v_end_xml_node.set('eid', prev_verses[-1].get('sid'))
+		chap_end_xml_node = ET.SubElement(parent_xml_node, "chapter")
+		chap_end_xml_node.set("eid", ref)
+	elif node.type == "v":
+		prev_verses = xml_root_node.findall(".//verse")
+		if len(prev_verses)>0:
+			if "sid" in prev_verses[-1].attrib:
+				v_end_xml_node = ET.SubElement(parent_xml_node, "verse")
+				v_end_xml_node.set('eid', prev_verses[-1].get('sid'))
+		verse_num_cap = USFM_LANGUAGE.query("(v (verseNumber) @vnum)").captures(node)[0]
+		verse_num = usfm_bytes[verse_num_cap[0].start_byte:verse_num_cap[0].end_byte].decode('utf-8')
+		v_xml_node = ET.SubElement(parent_xml_node, "verse")
+		ref = xml_root_node.findall('.//chapter')[-1].get('sid')+ ":"+ verse_num
+		v_xml_node.set('number', verse_num.strip())
+		v_xml_node.set('sid', ref.strip())
+	elif node.type == "verseText":
+		for child in node.children:
+			node_2_usx(child, usfm_bytes, parent_xml_node, xml_root_node)
+	elif node.type == 'paragraph':
+		para_tag_cap = USFM_LANGUAGE.query("(paragraph (_) @para-marker)").captures(node)[0]
+		para_marker = para_tag_cap[0].type
+		para_xml_node = ET.SubElement(parent_xml_node, "para")
+		para_xml_node.set("style", para_marker)
+		for child in para_tag_cap[0].children[1:]:
+			node_2_usx(child, usfm_bytes, para_xml_node, xml_root_node)
+	elif node.type in NOTE_MARKERS:
+		tag_node = node.children[0]
+		caller_node = node.children[1]
+		note_xml_node = ET.SubElement(parent_xml_node, "note")
+		note_xml_node.set("style",
+			usfm_bytes[tag_node.start_byte:tag_node.end_byte].decode('utf-8')
+			.replace("\\","").strip())
+		note_xml_node.set("caller",
+			usfm_bytes[caller_node.start_byte:caller_node.end_byte].decode('utf-8').strip())
+		for child in node.children[2:-1]:
+			node_2_usx(child, usfm_bytes, note_xml_node, xml_root_node)
+	elif node.type in CHAR_STYLE_MARKERS:
+		tag_node = node.children[0]
+		closing_node = None
+		children_range = len(node.children)
+		if node.children[-1].type.startswith('\\'):
+			closing_node = node.children[-1]
+			children_range = children_range-1
+		char_xml_node = ET.SubElement(parent_xml_node, "char")
+		char_xml_node.set("style",
+			usfm_bytes[tag_node.start_byte:tag_node.end_byte].decode('utf-8')
+			.replace("\\","").strip())
+		if closing_node is None:
+			char_xml_node.set("closed", "false")
+		else:
+			char_xml_node.set("closed", "true")
+		for child in node.children[1:children_range]:
+			node_2_usx(child, usfm_bytes, char_xml_node, xml_root_node)
+	elif node.type.endswith("Attribute"):
+		attrib_name_node= node.children[0]
+		attrib_name = usfm_bytes[attrib_name_node.start_byte:attrib_name_node.end_byte] \
+			.decode('utf-8').strip()
+		if attrib_name == "|":
+			attrib_name = DEFAULT_ATTRIB_MAP[node.parent.type]
+
+		attrib_val_cap = USFM_LANGUAGE.query("((attributeValue) @attrib-val)").captures(node)[0]
+		attrib_value = usfm_bytes[attrib_val_cap[0].start_byte:attrib_val_cap[0].end_byte] \
+			.decode('utf-8').strip()
+		parent_xml_node.set(attrib_name, attrib_value)
+	elif node.type == 'text':
+		text_val = usfm_bytes[node.start_byte:node.end_byte].decode('utf-8').strip()
+		siblings = parent_xml_node.findall("./*")
+		if len(siblings) > 0:
+			siblings[-1].tail = text_val
+		else:
+			parent_xml_node.text = text_val
+	elif node.type == "table":
+		table_xml_node = ET.SubElement(parent_xml_node, "table")
+		for child in node.children:
+			node_2_usx(child, usfm_bytes, table_xml_node, xml_root_node)
+	elif node.type == "tr":
+		row_xml_node = ET.SubElement(parent_xml_node, "row")
+		row_xml_node.set("style", "tr")
+		for child in node.children[1:]:
+			node_2_usx(child, usfm_bytes, row_xml_node, xml_root_node)
+	elif node.type in TABLE_CELL_MARKERS:
+		tag_node = node.children[0]
+		style = usfm_bytes[tag_node.start_byte:tag_node.end_byte].decode('utf-8')\
+		.replace("\\","").strip()
+		cell_xml_node = ET.SubElement(parent_xml_node, "cell")
+		cell_xml_node.set("style", style)
+		if "r" in style:
+			cell_xml_node.set("align", "end")
+		else:
+			cell_xml_node.set("align", "start")
+		for child in node.children[1:]:
+			node_2_usx(child, usfm_bytes, cell_xml_node, xml_root_node)
+	elif  node.type == "milestone":
+		# print(node.children)
+		ms_name_cap = USFM_LANGUAGE.query('''(
+			[(milestoneTag)
+			 (milestoneStartTag)
+			 (milestoneEndTag)
+			 ] @ms-name)''').captures(node)[0]
+		style = usfm_bytes[ms_name_cap[0].start_byte:ms_name_cap[0].end_byte].decode('utf-8')\
+		.replace("\\","").strip()
+		ms_xml_node = ET.SubElement(parent_xml_node, "ms")
+		ms_xml_node.set('style', style)
+		for child in node.children:
+			if child.type.endswith("Attribute"):
+				node_2_usx(child, usfm_bytes, ms_xml_node, xml_root_node)
+	elif node.type == "esb":
+		style = "esb"
+		sidebar_xml_node = ET.SubElement(parent_xml_node, "sidebar")
+		sidebar_xml_node.set("style", style)
+		for child in node.children[1:-1]:
+			node_2_usx(child, usfm_bytes, sidebar_xml_node, xml_root_node)
+	elif node.type == "cat":
+		cat_cap = USFM_LANGUAGE.query('((category) @category)').captures(node)[0]
+		category = usfm_bytes[cat_cap[0].start_byte:cat_cap[0].end_byte].decode('utf-8').strip()
+		parent_xml_node.set('category', category)
+	elif node.type == 'fig':
+		fig_xml_node = ET.SubElement(parent_xml_node, "figure")
+		fig_xml_node.set("style", 'fig')
+		for child in node.children[1:-1]:
+			node_2_usx(child, usfm_bytes, fig_xml_node, xml_root_node)
+	elif node.type == 'b':
+		break_xml_node = ET.SubElement(parent_xml_node, "optbreak")
+	elif (node.type in PARA_STYLE_MARKERS or 
+		  node.type.replace("\\","").strip() in PARA_STYLE_MARKERS):
+		tag_node = node.children[0]
+		style = usfm_bytes[tag_node.start_byte:tag_node.end_byte].decode('utf-8')
+		if style.startswith('\\'):
+			style = style.replace('\\','').strip()
+		else:
+			style = node.type
+		children_range_start = 1
+		if len(node.children)>1 and node.children[1].type.startswith("numbered"):
+			num_node = node.children[1]
+			num = usfm_bytes[num_node.start_byte:num_node.end_byte].decode('utf-8')
+			style += num
+			children_range_start = 2
+		para_xml_node = ET.SubElement(parent_xml_node, "para")
+		para_xml_node.set("style", style)
+		# caps = USFM_LANGUAGE.query('((text) @inner-text)').captures(node)
+		# para_xml_node.text = " ".join([usfm_bytes[txt_cap[0].start_byte:txt_cap[0].end_byte].decode('utf-8').strip()
+		#  for txt_cap in caps])
+		for child in node.children[children_range_start:]:
+			node_2_usx(child, usfm_bytes, para_xml_node, xml_root_node)
+	elif node.type.strip() in ["","|"]:
+		pass # skip white space nodes
+	elif len(node.children)>0:
+		for child in node.children:
+			node_2_usx(child, usfm_bytes, parent_xml_node, xml_root_node)
+	else:
+		raise Exception("Encountered unknown element ", str(node))
+
+
+
+
+
 
 def node_2_dict(node, usfm_bytes):
     '''recursive function converting an AST node and its children to dictionary'''
@@ -242,7 +454,11 @@ def toMarkDown(self, filt=Filter.SCRIPTURE_PARAGRAPHS.value):
 
 	def toUSX(self, filt=Filter.ALL):
 		'''convert the AST to the XML format USX'''
-		return "yet to be implemeneted"
+		usx_root = ET.Element("usx")
+		usx_root.set("version", "3.0")
+
+		node_2_usx(self.AST, self.USFMbytes, usx_root, usx_root)
+		return usx_root
 
 if __name__ == '__main__':
 	arg_parser = argparse.ArgumentParser(
@@ -281,7 +497,8 @@ def toUSX(self, filt=Filter.ALL):
 		table_output = my_parser.toTable(filt = output_filter)
 		print(csv_row_sep.join([csv_col_sep.join(row) for row in table_output]))
 	elif output_format == Format.USX:
-		print(my_parser.toUSX(filt = output_filter))
+		xmlstr = ET.tostring(my_parser.toUSX(filt = output_filter),encoding="unicode")	
+		print(minidom.parseString(xmlstr).toprettyxml(indent="   "))
 	elif output_format == Format.MD:
 		print(my_parser.toMarkDown(filt = output_filter))
 	elif output_format == Format.AST:
diff --git a/schemas/usx.rnc b/schemas/usx.rnc
new file mode 100644
index 00000000..6da38bd7
--- /dev/null
+++ b/schemas/usx.rnc
@@ -0,0 +1,765 @@
+# usx.rnc
+# A compact syntax Relax NG Schema for USX 3.0.7
+
+namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
+namespace xsi = "http://www.w3.org/2001/XMLSchema-instance"
+datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
+
+start = USX
+
+USX = ( Scripture | Peripheral )
+
+Scripture = (
+    element usx {
+        attribute version { xsd:string { minLength = "3" pattern = "\d+\.\d+(\.\d+)?"} },
+        attribute xsi:noNamespaceSchemaLocation { text }?,
+        
+        BookIdentification,
+        BookHeaders*,
+        BookTitles*,
+        BookIntroduction*,
+        BookIntroductionEndTitles*,
+        BookChapterLabel?,
+        Chapter,
+        # Chapter is used to separate intro from scripture text.
+        # All books will have chapter including the single chapter books: OBA, PHM, 2JN, 3JN, JUD
+        ChapterContent+
+    }
+    )
+
+Peripheral = (
+    element usx {
+        attribute version { xsd:string { minLength = "3" pattern = "\d+\.\d+(\.\d+)?"} },
+        attribute xsi:noNamespaceSchemaLocation { text }?,
+
+        (PeripheralBook | PeripheralDividedBook)
+    }
+    )
+
+PeripheralBook = (
+    PeripheralBookIdentification,
+    BookHeaders*,
+    BookTitles+,
+    BookIntroduction*,
+    BookIntroductionEndTitles*,
+    PeripheralContent+
+    )
+
+PeripheralDividedBook = (
+    PeripheralDividedBookIdentification,
+    
+    (PeripheralDivision | PeripheralOther)
+    
+    )
+
+BookIdentification =
+    element book {
+        attribute code { BookIdentification.book.code.enum }, # e.g. code="GEN"
+        attribute style { "id" },
+        text? # Brief description of the project (optional)
+    }
+BookIdentification.book.code.enum = (
+       "GEN" # Genesis
+     | "EXO" # Exodus
+     | "LEV" # Leviticus
+     | "NUM" # Numbers
+     | "DEU" # Deuteronomy
+     | "JOS" # Joshua
+     | "JDG" # Judges
+     | "RUT" # Ruth
+     | "1SA" # 1 Samuel
+     | "2SA" # 2 Samuel
+     | "1KI" # 1 Kings
+     | "2KI" # 2 Kings
+     | "1CH" # 1 Chronicles
+     | "2CH" # 2 Chronicles
+     | "EZR" # Ezra
+     | "NEH" # Nehemiah
+     | "EST" # Esther (Hebrew)
+     | "JOB" # Job
+     | "PSA" # Psalms
+     | "PRO" # Proverbs
+     | "ECC" # Ecclesiastes
+     | "SNG" # Song of Songs
+     | "ISA" # Isaiah
+     | "JER" # Jeremiah
+     | "LAM" # Lamentations
+     | "EZK" # Ezekiel
+     | "DAN" # Daniel (Hebrew)
+     | "HOS" # Hosea
+     | "JOL" # Joel
+     | "AMO" # Amos
+     | "OBA" # Obadiah
+     | "JON" # Jonah
+     | "MIC" # Micah
+     | "NAM" # Nahum
+     | "HAB" # Habakkuk
+     | "ZEP" # Zephaniah
+     | "HAG" # Haggai
+     | "ZEC" # Zechariah
+     | "MAL" # Malachi
+     | "MAT" # Matthew
+     | "MRK" # Mark
+     | "LUK" # Luke
+     | "JHN" # John
+     | "ACT" # Acts
+     | "ROM" # Romans
+     | "1CO" # 1 Corinthians
+     | "2CO" # 2 Corinthians
+     | "GAL" # Galatians
+     | "EPH" # Ephesians
+     | "PHP" # Philippians
+     | "COL" # Colossians
+     | "1TH" # 1 Thessalonians
+     | "2TH" # 2 Thessalonians
+     | "1TI" # 1 Timothy
+     | "2TI" # 2 Timothy
+     | "TIT" # Titus
+     | "PHM" # Philemon
+     | "HEB" # Hebrews
+     | "JAS" # James
+     | "1PE" # 1 Peter
+     | "2PE" # 2 Peter
+     | "1JN" # 1 John
+     | "2JN" # 2 John
+     | "3JN" # 3 John
+     | "JUD" # Jude
+     | "REV" # Revelation
+     | "TOB" # Tobit
+     | "JDT" # Judith
+     | "ESG" # Esther Greek
+     | "WIS" # Wisdom of Solomon
+     | "SIR" # Sirach (Ecclesiasticus)
+     | "BAR" # Baruch
+     | "LJE" # Letter of Jeremiah
+     | "S3Y" # Song of 3 Young Men
+     | "SUS" # Susanna
+     | "BEL" # Bel and the Dragon
+     | "1MA" # 1 Maccabees
+     | "2MA" # 2 Maccabees
+     | "3MA" # 3 Maccabees
+     | "4MA" # 4 Maccabees
+     | "1ES" # 1 Esdras (Greek)
+     | "2ES" # 2 Esdras (Latin)
+     | "MAN" # Prayer of Manasseh
+     | "PS2" # Psalm 151
+     | "ODA" # Odes
+     | "PSS" # Psalms of Solomon
+     | "EZA" # Apocalypse of Ezra
+     | "5EZ" # 5 Ezra
+     | "6EZ" # 6 Ezra
+     | "DAG" # Daniel Greek
+     | "PS3" # Psalms 152-155
+     | "2BA" # 2 Baruch (Apocalypse)
+     | "LBA" # Letter of Baruch
+     | "JUB" # Jubilees
+     | "ENO" # Enoch
+     | "1MQ" # 1 Meqabyan
+     | "2MQ" # 2 Meqabyan
+     | "3MQ" # 3 Meqabyan
+     | "REP" # Reproof
+     | "4BA" # 4 Baruch
+     | "LAO" # Laodiceans
+     )
+
+PeripheralBookIdentification =
+    element book {
+        attribute code { PeripheralBookIdentification.book.code.enum }, # e.g. code="CNC"
+        attribute style { "id" },
+        text? # Brief description of the project (optional)
+    }
+PeripheralBookIdentification.book.code.enum = (
+       "CNC" # Concordance
+     | "GLO" # Glossary
+     | "TDX" # Topical Index
+     | "NDX" # Names Index
+     )
+
+PeripheralDividedBookIdentification =
+    element book {
+        attribute code { PeripheralDividedBookIdentification.book.code.enum }, # e.g. code="FRT"
+        attribute style { "id" },
+        text? # Brief description of the project (optional)
+    }
+PeripheralDividedBookIdentification.book.code.enum = (
+       "XXA" # Extra A, e.g. a hymnal
+     | "XXB" # Extra B
+     | "XXC" # Extra C
+     | "XXD" # Extra D
+     | "XXE" # Extra E
+     | "XXF" # Extra F
+     | "XXG" # Extra G
+     | "FRT" # Front Matter
+     | "BAK" # Back Matter
+     | "OTH" # Other Matter
+     | "INT" # Introduction
+     )
+
+PeripheralDivision =
+    element periph {
+        attribute id { 
+              Peripheral.FRT.periph.id.enum
+            | Peripheral.INT.periph.id.enum
+            | Peripheral.BAK.periph.id.enum
+            | Peripheral.OTH.periph.id.enum
+            | xsd:string { pattern = "x\-[\p{L}_\-\.:]+" } },
+        attribute alt { text }?,
+        BookHeaders*,
+        BookTitles+,
+        BookIntroduction*,
+        BookIntroductionEndTitles*,
+        PeripheralContent*
+    }
+Peripheral.FRT.periph.id.enum = (
+      "title"
+    | "halftitle"
+    | "promo"
+    | "imprimatur"
+    | "pubdata"
+    | "foreword"
+    | "preface"
+    | "contents"
+    | "alphacontents"
+    | "abbreviations"
+    )
+Peripheral.INT.periph.id.enum = (
+      "intbible"
+    | "intot"
+    | "intpent"
+    | "inthistory"
+    | "intpoetry"
+    | "intprophesy"
+    | "intdc"
+    | "intnt"
+    | "intgospels"
+    | "intepistles"
+    | "intletters"
+    )
+Peripheral.BAK.periph.id.enum = (
+      "chron"
+    | "measures"
+    | "maps"
+    | "lxxquotes"
+    )
+Peripheral.OTH.periph.id.enum = (
+      "cover"
+    | "spine"
+    )
+
+PeripheralOther = (
+    BookHeaders*,
+    BookTitles*,
+    BookIntroduction*,
+    BookIntroductionEndTitles*,
+    PeripheralContent*
+    )
+
+BookHeaders =
+    element para {
+        attribute style { BookHeaders.para.style.enum }, text?
+    }
+BookHeaders.para.style.enum = (
+      "ide" # File encoding information
+    | "h" # Running header text for a book
+    | "h1" # Running header text (DEPRECATED)
+    | "h2" # Running header text, left side of page (DEPRECATED)
+    | "h3" # Running header text, right side of page (DEPRECATED)
+    | "toc1" # Long table of contents text
+    | "toc2" # Short table of contents text
+    | "toc3" # Book Abbreviation
+    | "toca1" # Alternative language long table of contents text
+    | "toca2" # Alternative language short table of contents text
+    | "toca3" # Alternative language book Abbreviation
+    | "rem" # Remark
+    | "usfm" # USFM markup version information (may occur if USX was generated from USFM)
+    )
+
+BookTitles =
+    element para {
+        attribute style { BookTitles.para.style.enum },
+        (Footnote | CrossReference | Char | Break | text)*
+    }
+BookTitles.para.style.enum = (
+      "mt" # The main title of the book (if single level)
+    | "mt1" # The main title of the book (if multiple levels)
+    | "mt2" # A secondary title usually occurring before the main title
+    | "mt3" # A tertiary title occurring after the main title
+    | "mt4"
+    | "imt" # Introduction major title, level 1 (if single level)
+    | "imt1" # Introduction major title, level 1 (if multiple levels)
+    | "imt2" # Introduction major title, level 2
+    | "rem" # Remark
+    )
+
+BookIntroduction =
+    element para {
+        attribute style { BookIntroduction.para.style.enum },
+        (Reference | Footnote | CrossReference | Char | IntroChar | Milestone | Figure | text)*
+    } | Table
+BookIntroduction.para.style.enum = (
+      "imt" # Introduction major title, level 1 - (if single level)
+    | "imt1" # Introduction major title, level 1 (if multiple levels)
+    | "imt2" # Introduction major title, level 2
+    | "imt3" # Introduction major title, level 3
+    | "imt4" # Introduction major title, level 4 (usually within parenthesis)
+    | "ib" # Introduction blank line
+    | "ie" # Introduction ending marker
+    | "ili" # A list entry, level 1 (if single level)
+    | "ili1" # A list entry, level 1 (if multiple levels)
+    | "ili2" # A list entry, level 2
+    | "im" # Introduction prose paragraph, with no first line indent (may occur after poetry)
+    | "imi" # Introduction prose paragraph text, indented, with no first line indent
+    | "imq" # Introduction prose paragraph, quote from the body text, with no first line indent
+    | "io" # Introduction outline text, level 1 (if single level)
+    | "io1" # Introduction outline text, level 1 (if multiple levels)
+    | "io2" # Introduction outline text, level 2
+    | "io3" # Introduction outline text, level 3
+    | "io4" # Introduction outline text, level 4
+    | "iot" # Introduction outline title
+    | "ip" # Introduction prose paragraph
+    | "ipi" # Introduction prose paragraph, indented, with first line indent
+    | "ipq" # Introduction prose paragraph, quote from the body text
+    | "ipr" # Introduction prose paragraph, right aligned
+    | "iq" # Introduction poetry text, level 1 (if single level)
+    | "iq1" # Introduction poetry text, level 1 (if multiple levels)
+    | "iq2" # Introduction poetry text, level 2
+    | "iq3" # Introduction poetry text, level 3
+    | "is" # Introduction section heading, level 1 (if single level)
+    | "is1" # Introduction section heading, level 1 (if multiple levels)
+    | "is2" # Introduction section heading, level 2
+    | "imte" # Introduction major title at introduction end, level 1 (if single level)
+    | "imte1" # Introduction major title at introduction end, level 1 (if multiple levels)
+    | "imte2" # Introduction major title at introduction end, level 2
+    | "iex" # Introduction explanatory or bridge text (e.g. explanation of missing book in Short Old Testament)
+    | "rem" # Remark
+    )
+
+BookIntroductionEndTitles =
+    element para {
+        attribute style { BookIntroductionEndTitles.para.style.enum },
+        (Footnote | CrossReference | Char | Milestone | Break | text)*
+    }
+BookIntroductionEndTitles.para.style.enum = (
+      "mt" # The main title of the book (if single level)
+    | "mt1" # The main title of the book (if multiple levels)
+    | "mt2" # A secondary title usually occurring before the main title
+    | "mt3" # A tertiary title occurring after the main title
+    | "mt4" # A small secondary title sometimes occuring within parentheses
+    | "imt" # Introduction major title, level 1 (if single level)
+    | "imt1" # Introduction major title, level 1 (if multiple levels)
+    | "imt2" # Introduction major title, level 2
+    )
+
+ChapterContent = (Chapter | Para | List | Table | Footnote | CrossReference | Sidebar)
+
+PeripheralContent = (Chapter | Para | List | Table | Footnote | CrossReference | Sidebar)
+
+Para =
+    element para {
+        attribute style { Para.para.style.enum },
+        attribute vid { xsd:string { pattern = "[A-Z1-4]{3} ?[a-z0-9,\-:\p{L}\p{Mn}]*" } }?,
+        (Reference | Footnote | CrossReference | Char | Milestone | Figure | Verse | Break | text)+
+    }
+Para.para.style.enum = (
+      "restore" # Comment about when text was restored
+    | "cls" # Closure of an Epistle
+    | "iex" # Introduction explanatory or bridge text (e.g. explanation of missing book in Short Old Testament)
+    | "ip" # Division or Section introductory paragraph (study Bible)
+    | "lit" # For a comment or note inserted for liturgical use
+    | "m" #  Paragraph text, with no first line indent (may occur after poetry) aka: Paragraph Continuation
+    | "mi" # Paragraph text, indented, with no first line indent; often used for discourse
+    | "nb" # Paragraph text, with no break from previous paragraph text (at chapter boundary)
+    | "p" # Paragraph text, with first line indent
+    | "pb" # Page Break used for new reader portions and children's bibles where content is controlled by the page
+    | "pc" # Inscription (paragraph text centered)
+    | "pi" # Paragraph text, level 1 indent (if single level), with first line indent; often used for discourse
+    | "pi1" # Paragraph text, level 1 indent (if multiple levels), with first line indent; often used for discourse
+    | "pi2" # Paragraph text, level 2 indent, with first line indent; often used for discourse
+    | "pi3" # Paragraph text, level 3 indent, with first line indent; often used for discourse
+    | "po" # Letter opening
+    | "pr" # Text refrain (paragraph right-aligned)
+    | "pmo" # Embedded text opening
+    | "pm" # Embedded text paragraph
+    | "pmc" # Embedded text closing
+    | "pmr" # Embedded text refrain
+    | "ph" # Paragraph text, with level 1 hanging indent (if single level) (DEPRECATED - use para@style li#)
+    | "ph1" # Paragraph text, with level 1 hanging indent (if multiple levels)
+    | "ph2" # Paragraph text, with level 2 hanging indent
+    | "ph3" # Paragraph text, with level 3 hanging indent
+    | "q" # Poetry text, level 1 indent (if single level)
+    | "q1" # Poetry text, level 1 indent (if multiple levels)
+    | "q2" # Poetry text, level 2 indent
+    | "q3" # Poetry text, level 3 indent
+    | "q4" # Poetry text, level 4 indent
+    | "qa" # Poetry text, Acrostic marker/heading
+    | "qc" # Poetry text, centered
+    | "qr" # Poetry text, Right Aligned
+    | "qm" # Poetry text, embedded, level 1 indent (if single level)
+    | "qm1" # Poetry text, embedded, level 1 indent (if multiple levels)
+    | "qm2" # Poetry text, embedded, level 2 indent
+    | "qm3" # Poetry text, embedded, level 3 indent
+    | "qd" # A Hebrew musical performance annotation, similar in content to Hebrew descriptive title.
+    | "b" # Poetry text stanza break (e.g. stanza break)
+    | "d" # A Hebrew text heading, to provide description (e.g. Psalms)
+    | "ms" # A major section division heading, level 1 (if single level)
+    | "ms1" # A major section division heading, level 1 (if multiple levels)
+    | "ms2" # A major section division heading, level 2
+    | "ms3" # A major section division heading, level 3
+    | "mr" # A major section division references range heading
+    | "r" # Parallel reference(s)
+    | "s" # A section heading, level 1 (if single level)
+    | "s1" # A section heading, level 1 (if multiple levels)
+    | "s2" # A section heading, level 2 (e.g. Proverbs 22-24)
+    | "s3" # A section heading, level 3 (e.g. Genesis "The First Day")
+    | "s4" # A section heading, level 4
+    | "sr" # A section division references range heading
+    | "sp" # A heading, to identify the speaker (e.g. Job)
+    | "sd" # Vertical space used to divide the text into sections, level 1 (if single level)
+    | "sd1" # Semantic division location (vertical space used to divide the text into sections), level 1 (if multiple levels)
+    | "sd2" # Semantic division location (vertical space used to divide the text into sections), level 2
+    | "sd3" # Semantic division location (vertical space used to divide the text into sections), level 3
+    | "sd4" # Semantic division location (vertical space used to divide the text into sections), level 4
+    | "ts" # Translator's chunk (to identify chunks of text suitable for translating at one time)
+    | "cp" # Published chapter number
+    | "cl" # Chapter label used for translations that add a word such as "Chapter"
+    | "cd" # Chapter Description (Publishing option D, e.g. in Russian Bibles)
+    | "mte" # The main title of the book repeated at the end of the book, level 1 (if single level)
+    | "mte1" # The main title of the book repeat /ed at the end of the book, level 1 (if multiple levels)
+    | "mte2" # A secondary title occurring before or after the 'ending' main title
+    | "p" # Front or back matter text paragraph, level 1
+    | "p1" # Front or back matter text paragraph, level 1 (if multiple levels)
+    | "p2" # Front or back matter text paragraph, level 2 (if multiple levels)
+    | "k1" # Concordance main entry text or keyword, level 1
+    | "k2" # Concordance main entry text or keyword, level 2
+    | "rem" # Remark
+    )
+
+List =
+    element para {
+        attribute style { List.para.style.enum },
+        attribute vid { xsd:string { pattern = "[A-Z1-4]{3} ?[a-z0-9\-:\p{L}\p{Mn}]*" } }?,
+        (Reference | Footnote | CrossReference | Char | ListChar | Milestone | Figure | Verse | Break | text)+
+    }
+List.para.style.enum = (
+      "lh" # List header (introductory remark)
+    | "li" # A list entry, level 1 (if single level)
+    | "li1" # A list entry, level 1 (if multiple levels)
+    | "li2" # A list entry, level 2
+    | "li3" # A list entry, level 3
+    | "li4" # A list entry, level 4
+    | "lf" # List footer (introductory remark)
+    | "lim" # An embedded list entry, level 1 (if single level)
+    | "lim1" # An embedded list entry, level 1 (if multiple levels)
+    | "lim2" # An embedded list entry, level 2
+    | "lim3" # An embedded list entry, level 3
+    | "lim4" # An embedded list entry, level 4
+    )
+
+Table = 
+    element table {
+        attribute vid { xsd:string { pattern = "[A-Z1-4]{3} ?[a-z0-9\-:\p{L}\p{Mn}]*" } }?,
+        element row {
+            attribute style {
+                ("tr") # Table row
+            },
+            (Verse | TableContent)+
+        }+
+    }
+TableContent =
+    element cell {
+        attribute style { xsd:string { pattern = "t[hc][rc]?\d+" } },
+        attribute align { cell.align.enum },
+        attribute colspan { xsd:integer }?,
+        (Footnote | CrossReference | Char | Milestone | Figure | Verse | Break | text)*
+    }
+           
+cell.align.enum = (
+      "start" # left in LTR, right in RTL
+    | "center" # center
+    | "end" # right in LTR, left in RTL
+    )
+
+IntroChar =
+    element char {
+        attribute style { IntroChar.char.style.enum },
+        char.closed?,
+        text,
+        (Reference | Char | IntroChar | Milestone | Footnote | Break | text)* # Nested character <char> markup
+    }
+IntroChar.char.style.enum = (
+      "ior" # Introduction references range for outline entry; for marking references separately
+    | "iqt" # For quoted scripture text appearing in the introduction
+    )
+
+Char = (
+    element char {
+        attribute style { Char.char.style.enum },
+        char.link?,
+        char.closed?,
+        text,
+        (Reference | Char | Milestone | Footnote | Break | text)* # Nested character <char> markup
+    }
+    | CharWithAttrib.enum
+    )
+Char.char.style.enum = (
+      "va" # Second (alternate) verse number (for coding dual numeration in Psalms; see also NRSV Exo 22.1-4)
+    | "vp" # Published verse marker - this is a verse marking that would be used in the published text
+    | "ca" # Second (alternate) chapter number
+    | "qac" # Poetry text, Acrostic markup of the first character of a line of acrostic poetry
+    | "qs" # Poetry text, Selah
+    | "add" # For a translational addition to the text
+    | "addpn" # For chinese words to be dot underline & underline (DEPRECATED - used nested char@style pn)
+    | "bk" # For the quoted name of a book
+    | "dc" # Deuterocanonical/LXX additions or insertions in the Protocanonical text
+    | "efm" # Reference to caller of previous footnote in a study Bible
+    | "fm" # Reference to caller of previous footnote
+    | "k" # For a keyword
+    | "nd" # For name of deity
+    | "ndx" # A subject index text item
+    | "ord" # For the text portion of an ordinal number
+    | "pn" # For a proper name
+    | "png" # For a geographic proper name
+    | "pro" # For indicating pronunciation in CJK texts (DEPRECATED - used char@style rb)
+    | "qt" # For Old Testament quoted text appearing in the New Testament
+    | "rq" # A cross-reference indicating the source text for the preceding quotation.
+    | "sig" # For the signature of the author of an Epistle
+    | "sls" # To represent where the original text is in a secondary language or from an alternate text source
+    | "tl" # For transliterated words
+    | "wg" # A Greek Wordlist text item
+    | "wh" # A Hebrew wordlist text item
+    | "wa" # An Aramaic wordlist text item
+    | "wj" # For marking the words of Jesus
+    | "xt" # A target reference(s)
+    | "jmp" # For associating linking attributes to a span of text
+    | "no" # A character style, use normal text
+    | "it" # A character style, use italic text
+    | "bd" # A character style, use bold text
+    | "bdit" # A character style, use bold + italic text
+    | "em" # A character style, use emphasized text style
+    | "sc" # A character style, for small capitalization text
+    | "sup" # A character style, for superscript text. Typically for use in critical edition footnotes.
+    )
+
+ListChar = (
+    element char {
+        attribute style { ListChar.char.style.enum },
+        char.link?,
+        char.closed?,
+        text,
+        (Reference | Char | Milestone | Footnote | Break | text)* # Nested character <char> markup
+    }
+    )
+ListChar.char.style.enum = (
+      "litl" # List entry total text
+    | "lik" # Structured list entry key text
+    | "liv" # Structured list entry value 1 content (if single value)
+    | "liv1" # Structured list entrt value 1 content (if multiple values)
+    | "liv2" # Structured list entry value 2 content
+    | "liv3" # Structured list entry value 3 content
+    | "liv4" # Structured list entry value 4 content
+    | "liv5" # Structured list entry value 5 content
+    )
+
+CharWithAttrib.enum = (
+      CharWithAttrib.char.style.w
+    | CharWithAttrib.char.style.rb
+    )
+
+CharWithAttrib.char.style.w = 
+    element char {
+        attribute style { "w" }, # A wordlist text item
+        char.link?,
+        char.closed?,
+        attribute lemma { xsd:string { minLength = "1" } }?, # Lemma/citation form found in glossary
+        attribute strong { xsd:string { pattern = "([HG]\d{4,5}(:[a-z])?,?)+"} }?, # Strong�s ID in the form H##### (Hebrew) or G##### (Greek)
+        attribute srcloc { xsd:string { pattern = "[a-z]{3,5}\d?:\d+\.\d+\.\d+\.\d+" } }?, # Location of the word in the source text
+        text,
+        (Reference | Char | Milestone | Footnote | Break | text)* # Nested character <char> markup
+    }
+
+CharWithAttrib.char.style.rb = 
+    element char {
+        attribute style { "rb" }, # Base text for ruby glosses
+        char.link?,
+        char.closed?,
+        attribute gloss { xsd:string { minLength = "1" } }?, # Ruby glosses
+        text,
+        (Reference | Char | Milestone | Footnote | Break | text)* # Nested character <char> markup
+    }
+
+char.link =
+    attribute link-href { xsd:string 
+        { pattern = "(.*///?(.*/?)+)|((prj:[A-Za-z\-0-9]{3,8} )?[A-Z1-4]{3} \d+:\d+(\-\d+)?)|(#[^\s]+)" } }?, # The resource being linked to as a URI
+    attribute link-title { xsd:string }?, # Plain text describing the remote resource such as might be shown in a tooltip
+    attribute link-id { xsd:string { pattern = "[\p{L}\d_\-\.:]+" } }? # Unique identifier for this location in the text
+
+char.closed =
+    attribute closed { xsd:boolean } # Present to allow roundtripping to USFM, not needed for publishing
+
+Milestone = (
+    element ms {
+        attribute style { Milestone.ms.style.enum },
+        attribute sid { xsd:string { pattern="[\p{L}\d_\-\.:]+" } }?, # optional milestone start id (digits, letters, underscore)
+        attribute eid { xsd:string { pattern="[\p{L}\d_\-\.:]+" } }?, # milestone end id (digits, letters, underscore) - required with sid
+        empty
+    }
+    | MilestoneWithAttrib.enum
+    )
+Milestone.ms.style.enum = (
+      xsd:string { pattern="ts?(\-[se])?" }
+    | xsd:string { pattern="[Zz].+" } # Only supporting "qt#-s|e" in USX 3.0. All other styles would be user generated.
+                                      # If user generated styles were created in USFM first, they would start with "z"
+    )
+
+MilestoneWithAttrib.enum = (
+      MilestoneWithAttrib.ms.style.qt
+    )
+MilestoneWithAttrib.ms.style.qt = 
+    element ms {
+        attribute style { xsd:string { pattern="qt[1-5]?(\-[se])?" } }, # Quotation speaker
+        attribute sid { xsd:string { pattern="[\p{L}\d_\-\.:]+" } }?, # Milestone start id for locating start and end pairs
+        attribute eid { xsd:string { pattern="[\p{L}\d_\-\.:]+" } }?, # Milestone end id for locating start and end pairs
+        attribute who { xsd:string { minLength = "2" } }?, # Name / identity of quote speaker
+        empty
+    }
+    # E.g. <ms style="qt1-s" sid="GEN.1.9.1" who="God">
+
+Chapter = ( ChapterStart | ChapterEnd )
+
+ChapterStart =
+    element chapter { # Start of new chapter
+        attribute number { xsd:integer }&
+        attribute style { "c" }&
+        attribute sid { xsd:string { minLength = "1" pattern = "[A-Z1-4]{3} ?[0-9]+" } }&
+        attribute altnumber { xsd:string { minLength = "1" pattern = "[0-9]+\p{L}?(\x{200F}?[\-,][0-9]+\p{L}?)*" } }?&
+        attribute pubnumber { xsd:string { minLength = "1" } }?&
+        empty
+    }
+    # E.g. <chapter number="1" style="c" altnumber="2" pubnumber="A" />
+    # This schema will not support a chapter:verse string within altnumber
+
+ChapterEnd =
+    element chapter { # End of chapter
+        attribute eid { xsd:string { minLength = "1" pattern = "[A-Z1-4]{3} ?[0-9]+" } },
+        empty
+    }
+    
+BookChapterLabel =
+    element para { 
+        attribute style { "cl" }, # Chapter label for book, specified once prior to chapter 1
+        text
+    }
+
+Verse = ( VerseStart | VerseEnd )
+
+VerseStart =
+    element verse {
+        attribute number { xsd:string { minLength = "1" pattern = "[0-9]+[\p{L}\p{Mn}]*(.?[\-,][0-9]+[\p{L}\p{Mn}]*)*" } },
+        attribute style { "v" },
+        attribute altnumber { xsd:string { minLength = "1" pattern = "[0-9]+[\p{L}\p{Mn}]*(.?[\-,:][0-9]+[\p{L}\p{Mn}]*)*" } }?,
+        attribute pubnumber { xsd:string { minLength = "1" } }?,
+        attribute sid { xsd:string { pattern = "[A-Z1-4]{3} ?[a-z0-9,\-:\p{L}\p{Mn}]*" } },
+        empty
+    }
+    # E.g. <verse number="1" style="v" altnumber="2" pubnumber="B" sid="GEN 1:22" />
+
+VerseEnd =
+    element verse {
+        attribute eid { xsd:string { pattern = "[A-Z1-4]{3} ?[a-z0-9,\-:\p{L}\p{Mn}]*" } },
+        empty
+    }
+
+Footnote =
+    element note {
+        attribute style { "f" | "fe" | "ef" },
+        attribute caller { text },
+        (attribute category { text })?,
+        (FootnoteChar | text )+
+    }
+
+FootnoteChar =
+    element char {
+        attribute style { FootnoteChar.char.style.enum },
+        char.link?,
+        char.closed?,
+        (Char | FootnoteChar | FootnoteVerse | Reference | text)+
+    }
+FootnoteChar.char.style.enum = (
+      "fr" # The origin reference for the footnote
+    | "cat" # Note category (study Bible)
+    | "ft" # Footnote text, Protocanon
+    | "fk" # A footnote keyword
+    | "fq" # A footnote scripture quote or alternate rendering
+    | "fqa" # A footnote alternate rendering for a portion of scripture text
+    | "fl" # A footnote label text item, for marking or "labelling" the type or alternate translation being provided in the note.
+    | "fw" # A footnote witness list, for distinguishing a list of sigla representing witnesses in critical editions.
+    | "fp" # A Footnote additional paragraph marker
+    | "fv" # A verse number within the footnote text
+    | "fdc" # Footnote text, applies to Deuterocanon only (DEPRECATED - use char@style dc)
+    | "xt" # A cross reference target reference(s)
+    | "it" # A character style, use italic text
+    | "bd" # A character style, use bold text
+    | "bdit" # A character style, use bold + italic text
+    | "em" # A character style, use emphasized text style
+    | "sc" # A character style, for small capitalization text; abbreviations
+    )
+
+FootnoteVerse =
+    element char {
+        attribute style { "fv" },
+        text
+    }
+
+CrossReference =
+    element note {
+        attribute style { "x" | "ex" },
+        attribute caller { text },
+        (CrossReferenceChar | text)+
+    }
+
+CrossReferenceChar =
+    element char {
+        attribute style { CrossReferenceChar.char.style.enum },
+        char.link?,
+        char.closed?,
+        (Char | CrossReferenceChar | Reference | text)+
+    }
+CrossReferenceChar.char.style.enum = (
+      "xo" # The cross reference origin reference
+    | "xop" # Published cross reference origin text (origin reference that should appear in the published text)
+    | "xt" # The cross reference target reference(s), protocanon only
+    | "xta" # Cross reference target references added text
+    | "xk" # A cross reference keyword
+    | "xq" # A cross-reference quotation from the scripture text
+    | "xot" # Cross-reference target reference(s), Old Testament only
+    | "xnt" # Cross-reference target reference(s), New Testament only
+    | "xdc" # Cross-reference target reference(s), Deuterocanon only (DEPRECATED - use char@style dc)
+    )
+
+Sidebar =
+    element sidebar {
+        attribute style { "esb" },
+        (attribute category { text })?,
+        (Para | List | Table | Footnote | CrossReference)+
+    }
+
+Figure =
+    element figure {
+        attribute style { text },
+        attribute alt { text }?,
+        attribute file { text },
+        attribute size { text }?,
+        attribute loc { text }?,
+        attribute copy { text }?,
+        attribute ref { text }?,
+        text? # Caption
+    }
+
+Reference =
+    element ref {
+        attribute loc { xsd:string { pattern = "[A-Z1-4]{3}(-[A-Z1-4]{3})? ?[a-z0-9\-:]*" } },
+        text?
+    }
+
+Break = 
+    element optbreak { empty }
diff --git a/tree-sitter-usfm/Notes_on_grammar_building.md b/tree-sitter-usfm/Notes_on_grammar_building.md
index 3b82db17..62fabf55 100644
--- a/tree-sitter-usfm/Notes_on_grammar_building.md
+++ b/tree-sitter-usfm/Notes_on_grammar_building.md
@@ -16,8 +16,49 @@
 14. Making some paragraph marker mandatory after titles within chapter. All example in onlice docs in title headings section.
 15. \th & \thr markers(table heading cells) and \tc & \tcr(regular table cells) all have similar settings in sty. So their relative positions are not determined by rules. Also, ordering of tc1, tc2, tc3 etc is not specified via rank values. Hence in our rules also, we let them to occur in any order.
 16. Related to footnotes, though usfm doc shows \fr as occuring first inside the footnotes contents, there is no rank value indicating that in usfm.sty. So in our grammar, giving all footnote content markers, including \fr, equal privilege to occur in any order with the footnote.
-17. Can any character be used as footnote caller? In doc it says, it may be one of +, -, ?. But the examples in the doc itself shows usages of c, ⸀, ° 
+17. Can any character be used as footnote caller? In doc it says, it may be one of +, -, ?. But the examples in the doc itself shows usages of c, ⸀, ° .(Resolved. USX doc says: may be any single character or sequence of characters preferred as the caller for a note.)
 18. Maker ex not specified in usfm.sty. 
 19. From documentaion I feel that, any name can be given to a milestone(as long as it is self closing) and not just qt and ts. So not adding qt and ts are spec defined markers in the grammar even though I see them listed in the sty file.
 20. As per the rank value ie(6) seems to be expected before imte(7), but as per the decsription and example in the documentation I think it is expected even after imte. So implementing it like that in our grammar
 21. We are expecting a space or newline after paragraph marker names even though usfm doc says it is optional. reason: \pr or \pc may match \p and the next letter may get treated as text. This can be re-visited later.
+22. Can a marker have multiple [default attributes](https://ubsicap.github.io/usfm/attributes/index.html#default-attribute) listed in a comma or pipe(|) separated way?
+
+## USX Related
+23. Is there any rule on order of closing of paragraph marker and verse marker?
+```
+<usx version="3.0">
+  <book code="GEN" style="id" />
+  <chapter number="1" style="c" sid="GEN 1" />
+  <para style="p">
+    <verse number="1" style="v" sid="GEN 1:1" />
+    verse one <verse eid="GEN 1:1" />
+    <verse number="2" style="v" sid="GEN 1:2" />
+    verse two
+    <verse eid="GEN 1:2" />
+    </para>
+  <chapter eid="GEN 1" />
+</usx>
+```
+or
+```
+<usx version="3.0">
+  <book code="GEN" style="id" />
+  <chapter number="1" style="c" sid="GEN 1" />
+  <para style="p">
+    <verse number="1" style="v" sid="GEN 1:1" />
+    verse one 
+    <verse eid="GEN 1:1" />
+    <verse number="2" style="v" sid="GEN 1:2" />
+    verse two
+    </para>
+    <verse eid="GEN 1:2" />  
+  <chapter eid="GEN 1" />
+</usx>
+```
+Is there one correct usage, or are both acceptable?
+The [rnc grammar](https://github.com/ubsicap/usx/blob/6c490bb5675d281b0fa01876fe67f6e3fd50a4ce/schema/usx.rnc) for USX accepts both order. If both are valid, one USFM file can have different USX representations.
+
+24. As per the [USX rnc grammar](https://github.com/ubsicap/usx/blob/6c490bb5675d281b0fa01876fe67f6e3fd50a4ce/schema/usx.rnc#L13) at least one `BookTitles` marker is mandatory. Is this rule to be enforced?
+The [USX doc](https://ubsicap.github.io/usx/structure.html#scripture) also says the same.
+25. The [fm marker](https://ubsicap.github.io/usfm/notes_basic/fnotes.html#fm-fm) is not mentioned in USX docs.
+26. Is there a USFM marker corresponding to the [ref element](https://ubsicap.github.io/usx/elements.html#ref) in USX? IT doesn't even have style, valid in or parent info provided in the docs.
diff --git a/tree-sitter-usfm/grammar.js b/tree-sitter-usfm/grammar.js
index 63ccf5d1..1befd1fd 100644
--- a/tree-sitter-usfm/grammar.js
+++ b/tree-sitter-usfm/grammar.js
@@ -50,8 +50,8 @@ module.exports = grammar({
     tocBlock: $ => prec.right(0,repeat1($.toc)),
     tocaBlock: $ =>prec.right(0, repeat1($.toca)),//only under some hmarkers
 
-    h: $ => seq($._hTag, $.text),
-    _hTag: $ => seq("\\h",optional($.numberedLevelMax3), " "),
+    h: $ => seq($.hTag, $.text),
+    hTag: $ => seq("\\h",optional($.numberedLevelMax3), " "),
     toc: $ => seq("\\toc",optional($.numberedLevelMax3), " ", $.text),
     toca: $ => seq("\\toca",optional($.numberedLevelMax3), " ", $.text),
 
@@ -75,20 +75,20 @@ module.exports = grammar({
 
     iqt: $ => seq("\\iqt ", $.text, "\\iqt*"),
     imtBlock: $ => prec.right(0,repeat1($.imt)),
-    imt: $ => prec.right(0, seq($._imtTag, $._introText)),
-    _imtTag: $ => seq("\\imt",optional($.numberedLevelMax4), " "),
+    imt: $ => prec.right(0, seq($.imtTag, $._introText)),
+    imtTag: $ => seq("\\imt",optional($.numberedLevelMax4), " "),
     imteBlock: $ => prec.right(0,repeat1($.imte)),
-    imte: $ => prec.right(0, seq($._imteTag, $._introText)),
-    _imteTag: $ => seq("\\imte",optional(token.immediate(/[12]/)), " "),
+    imte: $ => prec.right(0, seq($.imteTag, $._introText)),
+    imteTag: $ => seq("\\imte",optional(token.immediate(/[12]/)), " "),
     _midIntroMarker: $ => choice($.isBlock, $.io, $.iot, $.ip, $.im,
       $.ipi, $.imi, $.iliBlock, $.ipq, $.imq, $.ipr, $.ib,
       $.iqBlock, $.iex, $._comments, $.milestone, $.zNameSpaceRegular, $.esb),
     isBlock: $ => prec.right(0,repeat1($.is)),
-    is: $ => prec.right(0, seq($._isTag, $._introText)),
-    _isTag: $ => seq("\\is",optional(token.immediate(/[12]/)), " "),
+    is: $ => prec.right(0, seq($.isTag, $._introText)),
+    isTag: $ => seq("\\is",optional(token.immediate(/[12]/)), " "),
     ioBlock: $ => prec.right(0,repeat1($.io)),
-    io: $ => prec.right(0, seq($._ioTag, $._introText, optional($.ior))),
-    _ioTag: $ => seq("\\io",optional($.numberedLevelMax4), " "),
+    io: $ => prec.right(0, seq($.ioTag, $._introText, optional($.ior))),
+    ioTag: $ => seq("\\io",optional($.numberedLevelMax4), " "),
     ior: $ => seq("\\ior ", $.text, "\\ior*"),
     iot: $ => prec.right(0, seq("\\iot ", $._introText)),
     ip: $ => prec.right(0, seq("\\ip ", $._introText)),
@@ -96,15 +96,15 @@ module.exports = grammar({
     ipi: $ => prec.right(0, seq("\\ipi ", $._introText)),
     imi: $ => prec.right(0, seq("\\imi ", $._introText)),
     iliBlock: $ => prec.right(0,repeat1($.ili)),
-    ili: $ => prec.right(0, seq($._iliTag, $._introText)),
-    _iliTag: $ => seq("\\ili",optional(token.immediate(/[12]/)), " "),
+    ili: $ => prec.right(0, seq($.iliTag, $._introText)),
+    iliTag: $ => seq("\\ili",optional(token.immediate(/[12]/)), " "),
     ipq: $ => prec.right(0, seq("\\ipq ", $._introText)),
     imq: $ => prec.right(0, seq("\\imq ", $._introText)),
     ipr: $ => prec.right(0, seq("\\ipr ", $._introText)),
     ib: $ => seq("\\ib"),
     iqBlock: $ => prec.right(0,repeat1($.iq)),
-    iq: $ => prec.right(0, seq($._iqTag, $._introText)),
-    _iqTag: $ => seq("\\iq",optional($.numberedLevelMax3), " "),
+    iq: $ => prec.right(0, seq($.iqTag, $._introText)),
+    iqTag: $ => seq("\\iq",optional($.numberedLevelMax3), " "),
     ie: $ => seq("\\ie"),
     iex: $ => prec.right(0, seq("\\iex ", $._introText)), // can occur in introduction or inside chapter
 
@@ -176,39 +176,39 @@ module.exports = grammar({
     ),
 
     mtBlock: $ => prec.right(0,repeat1($.mt)),
-    mt: $ => seq($._mtTag, repeat1(choice($.text,
+    mt: $ => seq($.mtTag, repeat1(choice($.text,
       $.footnote, $.crossref      
       ))),
-    _mtTag: $ => seq("\\mt",optional($.numberedLevelMax4), " "),
+    mtTag: $ => seq("\\mt",optional($.numberedLevelMax4), " "),
 
     mteBlock: $ => prec.right(0,repeat1($.mte)),
-    mte: $ => prec.right(0, seq($._mteTag, repeat1(choice($.text,
+    mte: $ => prec.right(0, seq($.mteTag, repeat1(choice($.text,
       $.footnote, $.crossref      
       )))),
-    _mteTag: $ => seq("\\mte",optional(token.immediate(/[12]/)), " "),
+    mteTag: $ => seq("\\mte",optional(token.immediate(/[12]/)), " "),
 
     msBlock: $ => prec.right(0, repeat1($.ms)),
-    ms: $ => prec.right(0, seq($._msTag, repeat1(choice($.text,
+    ms: $ => prec.right(0, seq($.msTag, repeat1(choice($.text,
       $.footnote, $.crossref,
       $._characterMarker      
       )), optional($.mr))),
-    _msTag: $ => seq("\\ms",optional($.numberedLevelMax3), " "),
+    msTag: $ => seq("\\ms",optional($.numberedLevelMax3), " "),
     mr: $ => seq("\\mr ", $.text),
 
     sBlock: $ => prec.right(0, repeat1($.s)),
-    s: $ => prec.right(0, seq($._sTag, repeat(choice($.text,
+    s: $ => prec.right(0, seq($.sTag, repeat(choice($.text,
       $.footnote, $.crossref, 
       $._characterMarker      
       )), optional($.sr), optional($.r))),
-    _sTag: $ => seq("\\s",optional($.numberedLevelMax5), " "),
+    sTag: $ => seq("\\s",optional($.numberedLevelMax5), " "),
     sr: $ => seq("\\sr ", $.text),
     r: $ => seq("\\r ", $.text), // ocurs under c too
 
     sp: $ => seq("\\sp ", $.text),
     d: $ => seq("\\d ", $.text),
     sdBlock: $ => prec.right(0, repeat1($.sd)),
-    sd: $ => seq($._sdTag),
-    _sdTag: $ => seq("\\sd", optional($.numberedLevelMax4), $._spaceOrLine),
+    sd: $ => seq($.sdTag),
+    sdTag: $ => seq("\\sd", optional($.numberedLevelMax4), $._spaceOrLine),
     // rqMarker implemented in cross ref section 
 
     // paragraph
@@ -250,14 +250,14 @@ module.exports = grammar({
     pmc: $ => prec.right(0, seq("\\pmc", $._spaceOrLine, repeat($._paragraphContent))),
     pmr: $ => prec.right(0, seq("\\pmr", $._spaceOrLine, repeat($._paragraphContent))),
     piBlock: $ => prec.right(0, repeat1($.pi)),
-    pi: $ => prec.right(0, seq($._piTag, repeat($._paragraphContent))),
-    _piTag: $ => seq("\\pi", optional($.numberedLevelMax3), $._spaceOrLine),
+    pi: $ => prec.right(0, seq($.piTag, repeat($._paragraphContent))),
+    piTag: $ => seq("\\pi", optional($.numberedLevelMax3), $._spaceOrLine),
     mi: $ => prec.right(0, seq("\\mi", $._spaceOrLine, repeat($._paragraphContent))),
     nb: $ => prec.right(0, seq("\\nb", $._spaceOrLine, repeat($._paragraphContent))),
     pc: $ => prec.right(0, seq("\\pc", $._spaceOrLine, repeat($._paragraphContent))),
     phBlock: $ => prec.right(0, repeat1($.ph)),
-    ph: $ => prec.right(0, seq($._phTag, repeat($._paragraphContent))),
-    _phTag: $ => seq("\\ph", optional($.numberedLevelMax3), $._spaceOrLine),
+    ph: $ => prec.right(0, seq($.phTag, repeat($._paragraphContent))),
+    phTag: $ => seq("\\ph", optional($.numberedLevelMax3), $._spaceOrLine),
     phi: $ => prec.right(0, seq("\\phi", $._spaceOrLine, repeat($._paragraphContent))),
     b: $ => seq("\\b", $._spaceOrLine),
 
@@ -280,16 +280,16 @@ module.exports = grammar({
     ),
 
     qBlock: $ => prec.right(0, repeat1($.q)),
-    q: $ => prec.right(0, seq($._qTag, repeat($._poetryContent))),
-    _qTag: $ => seq("\\q", optional($.numberedLevelMax3), $._spaceOrLine),
+    q: $ => prec.right(0, seq($.qTag, repeat($._poetryContent))),
+    qTag: $ => seq("\\q", optional($.numberedLevelMax3), $._spaceOrLine),
     qr: $ => prec.right(0, seq("\\qr", $._spaceOrLine, repeat($._poetryContent))),
     qc: $ => prec.right(0, seq("\\qc",$._spaceOrLine, repeat($._poetryContent))),
     qs: $ => seq("\\qs", $._spaceOrLine, repeat($._poetryContent), "\\qs*"),
     qa: $ => prec.right(0, seq("\\qa",$._spaceOrLine, repeat($._poetryContent))),
     qac: $ => seq("\\qac", $._spaceOrLine, repeat($._poetryContent), token("\\qac*")),
     qmBlock: $ => prec.right(0, repeat1($.qm)),
-    qm: $ => prec.right(0, seq($._qmTag, repeat($._poetryContent))),
-    _qmTag: $ => seq("\\qm", optional($.numberedLevelMax3), $._spaceOrLine),
+    qm: $ => prec.right(0, seq($.qmTag, repeat($._poetryContent))),
+    qmTag: $ => seq("\\qm", optional($.numberedLevelMax3), $._spaceOrLine),
     qd: $ => prec.right(0, seq("\\qd",$._spaceOrLine, repeat($._poetryContent))),
 
     //List
@@ -299,21 +299,21 @@ module.exports = grammar({
     lf: $ => prec.right(0, seq("\\lf", $._spaceOrLine, repeat($._paragraphContent))),
     _listMarker: $ => choice( $.liBlock, $.limBlock ), 
     liBlock: $ => prec.right(0, repeat1($.li)),
-    li: $ => prec.right(0, seq($._liTag, repeat(choice(
+    li: $ => prec.right(0, seq($.liTag, repeat(choice(
       $._paragraphContent,
       $._listTextContent,
       )))),
-    _liTag: $ => seq("\\li",optional($.numberedLevelMax4), $._spaceOrLine),
+    liTag: $ => seq("\\li",optional($.numberedLevelMax4), $._spaceOrLine),
     limBlock: $ => prec.right(0, repeat1($.lim)),
-    lim: $ => prec.right(0, seq($._limTag, repeat(choice(
+    lim: $ => prec.right(0, seq($.limTag, repeat(choice(
         $._paragraphContent,
         $._listTextContent,)
       ))
     ),
-    _limTag: $ => seq("\\lim",optional($.numberedLevelMax4), $._spaceOrLine),
-    liv: $ => prec.right(0, seq($._livTag, $._spaceOrLine, repeat(choice($.verseText,
-      )), $._livTag, token.immediate("*") )),
-    _livTag: $ => prec.right(0,seq("\\liv",optional($.numberedLevelMax5))),
+    limTag: $ => seq("\\lim",optional($.numberedLevelMax4), $._spaceOrLine),
+    liv: $ => prec.right(0, seq($.livTag, $._spaceOrLine, repeat(choice($.verseText,
+      )), $.livTag, token.immediate("*") )),
+    livTag: $ => prec.right(0,seq("\\liv",optional($.numberedLevelMax5))),
     lik: $ => seq("\\lik ", $.verseText, "\\lik*"),
     litl: $ => seq("\\litl ", $.verseText, "\\litl*"),
 
@@ -337,13 +337,17 @@ module.exports = grammar({
       $.tc,
       $.tcr))
     )),
-    th: $=> seq("\\th",optional(token.immediate(/[12345](-[12345])?/)), $._spaceOrLine, $._tableText),
-    thr: $=> seq("\\thr",optional(token.immediate(/[12345](-[12345])?/)), $._spaceOrLine, $._tableText),
-    tc: $=> seq("\\tc",optional(token.immediate(/[12345](-[12345])?/)), $._spaceOrLine, $._tableText),
-    tcr: $=> seq("\\tcr",optional(token.immediate(/[12345](-[12345])?/)), $._spaceOrLine, $._tableText),
+    thTag: $=> /\\th([12345](-[12345])?)?/,
+    thrTag: $=> /\\thr([12345](-[12345])?)?/,
+    tcTag: $=> /\\tc([12345](-[12345])?)?/,
+    tcrTag: $=> /\\tcr([12345](-[12345])?)?/,
+    th: $=> seq($.thTag, $._spaceOrLine, $._tableText),
+    thr: $=> seq($.thrTag, $._spaceOrLine, $._tableText),
+    tc: $=> seq($.tcTag, $._spaceOrLine, $._tableText),
+    tcr: $=> seq($.tcrTag, $._spaceOrLine, $._tableText),
 
     //Footnote
-    caller: $ => /[^\s\\]/,
+    caller: $ => /[^\s\\]+/,
     noteText: $ => prec.right(0, repeat1(choice($.text,
       $._nestedCharacterMarker,
       ))),
@@ -558,12 +562,15 @@ module.exports = grammar({
     /* since milestones can be user defined, their name is defined as any set of 
       letters of digits.*/
 
-    _milestoneStandaloneMarker: $ => seq("\\", prec.right(1,token.immediate(/[\w\d_]+/)),
+    milestoneTag: $=> seq("\\", prec.right(1,token.immediate(/[\w\d_]+/))),
+    _milestoneStandaloneMarker: $ => seq($.milestoneTag,
       optional($._milestoneAttributes), "\\*" ),
 
-    _milestoneStart: $ => seq(/\\[\w\d_]+-s/,
+    milestoneStartTag: $ => /\\[\w\d_]+-s/,
+    milestoneEndTag: $=> /\\[\w\d_]+-e/,
+    _milestoneStart: $ => seq($.milestoneStartTag,
       optional($._milestoneAttributes), "\\*" ),
-    _milestoneEnd: $ => seq(/\\[\w\d_]+-e/,
+    _milestoneEnd: $ => seq($.milestoneEndTag,
       optional($._milestoneAttributes), "\\*" ),
 
     /* dont tie up the start and end in the grammar as of now.
@@ -575,9 +582,11 @@ module.exports = grammar({
 
     milestone: $ => choice($._milestoneStart, $._milestoneEnd, $._milestoneStandaloneMarker),
 
-    zNameSpaceRegular: $ => prec.right(0, seq(/\\z[\w\d_-]+/, optional($.text))),
-    zNameSpaceClosed: $ => prec.right(0, seq(/\\z[\w\d_-]+/, optional($.text),
-      optional($._milestoneAttributes), /\\z[\w\d_-]+\*/)), // This may not support one name space within another
+    zSpaceTag: $=> /\\z[\w\d_-]+/,
+    _zSpaceClose: $=> /\\z[\w\d_-]+\*/,
+    zNameSpaceRegular: $ => prec.right(0, seq($.zSpaceTag, optional($.text))),
+    zNameSpaceClosed: $ => prec.right(0, seq($.zSpaceTag, optional($.text),
+      optional($._milestoneAttributes), $._zSpaceClose)), // This may not support one name space within another
     
     esb: $ => seq("\\esb",  repeat($._esbContents), "\\esbe"),
     _esbContents: $ => choice( 
@@ -595,7 +604,8 @@ module.exports = grammar({
       $.ip,
       ),
 
-    cat: $ => seq("\\cat", /[\w\d\s]+/, "\\cat*"),
+    category: $=> /[\w\d\s]+/,
+    cat: $ => seq("\\cat", $.category, "\\cat*"),
       
     /* *****Attributes******** */
 


Test patch:
diff --git a/tree-sitter-usfm/test/corpus/test10_milestones.txt b/tree-sitter-usfm/test/corpus/test10_milestones.txt
index 804ee802..6296135c 100644
--- a/tree-sitter-usfm/test/corpus/test10_milestones.txt
+++ b/tree-sitter-usfm/test/corpus/test10_milestones.txt
@@ -32,23 +32,27 @@ surprised.
         (verseText
           (text))
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
         (verseText
           (text))
-        (milestone)
+        (milestone
+          (milestoneEndTag))
         (verseText
           (text))))
     (paragraph
       (p
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
         (verseText
           (text))
-        (milestone)
+        (milestone
+          (milestoneEndTag))
         (verseText
           (text))
         (v
@@ -62,12 +66,14 @@ surprised.
         (verseText
           (text))
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
         (verseText
           (text))
-        (milestone)
+        (milestone
+          (milestoneEndTag))
         (verseText
           (text))))
     (paragraph
@@ -115,6 +121,7 @@ a man he has chosen. He has given proof of this to everyone by raising that man
         (verseText
           (text))
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue))
@@ -134,14 +141,17 @@ a man he has chosen. He has given proof of this to everyone by raising that man
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (milestone
+            (milestoneStartTag)
             (msAttribute
               (milestoneAttributeName)
               (attributeValue)))
           (verseText
             (text))
-          (milestone)
+          (milestone
+            (milestoneEndTag))
           (verseText
             (text)))))
     (paragraph
@@ -153,14 +163,17 @@ a man he has chosen. He has given proof of this to everyone by raising that man
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (milestone
+            (milestoneStartTag)
             (msAttribute
               (milestoneAttributeName)
               (attributeValue)))
           (verseText
             (text))
-          (milestone)
+          (milestone
+            (milestoneEndTag))
           (verseText
             (text)))))
     (paragraph
@@ -176,6 +189,7 @@ a man he has chosen. He has given proof of this to everyone by raising that man
         (verseText
           (text))
         (milestone
+          (milestoneEndTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))))))
@@ -209,7 +223,8 @@ authority, and they say evil things about the glorious ones.
   (chapter
     (c
       (chapterNumber))
-    (milestone)
+    (milestone
+      (milestoneTag))
     (paragraph
       (p
         (v
@@ -220,7 +235,8 @@ authority, and they say evil things about the glorious ones.
           (verseNumber))
         (verseText
           (text))
-        (milestone)
+        (milestone
+          (milestoneTag))
         (verseText
           (text))
         (v
@@ -231,7 +247,8 @@ authority, and they say evil things about the glorious ones.
           (verseNumber))
         (verseText
           (text))
-        (milestone)))))
+        (milestone
+          (milestoneTag))))))
 
 ================================================================================
 ts milestone w/ attributes
@@ -272,6 +289,7 @@ authority, and they say evil things about the glorious ones.
         (verseText
           (text))
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
@@ -288,12 +306,14 @@ authority, and they say evil things about the glorious ones.
         (verseText
           (text))
         (milestone
+          (milestoneEndTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
         (verseText
           (text))
         (milestone
+          (milestoneStartTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))
@@ -308,6 +328,7 @@ authority, and they say evil things about the glorious ones.
         (verseText
           (text))
         (milestone
+          (milestoneEndTag)
           (msAttribute
             (milestoneAttributeName)
             (attributeValue)))))))
diff --git a/tree-sitter-usfm/test/corpus/test11_namespace_esb.txt b/tree-sitter-usfm/test/corpus/test11_namespace_esb.txt
index 6409f04d..4ac0ff36 100644
--- a/tree-sitter-usfm/test/corpus/test11_namespace_esb.txt
+++ b/tree-sitter-usfm/test/corpus/test11_namespace_esb.txt
@@ -18,6 +18,7 @@ znamespace
       (bookcode)
       (description)))
   (zNameSpaceRegular
+    (zSpaceTag)
     (text))
   (chapter
     (c
@@ -25,12 +26,14 @@ znamespace
     (paragraph
       (p
         (zNameSpaceRegular
+          (zSpaceTag)
           (text))
         (v
           (verseNumber))
         (verseText
           (text)
           (zNameSpaceClosed
+            (zSpaceTag)
             (text))
           (text))
         (v
@@ -42,6 +45,7 @@ znamespace
         (verseText
           (text)
           (zNameSpaceClosed
+            (zSpaceTag)
             (text)
             (customAttribute
               (customAttributeName)
@@ -96,6 +100,7 @@ the sons of Zebedee.
       (title
         (msBlock
           (ms
+            (msTag)
             (text))))
       (paragraph
         (p
@@ -173,10 +178,12 @@ the sons of Zebedee.
         (verseText
           (text))))
     (esb
-      (cat)
+      (cat
+        (category))
       (title
         (msBlock
           (ms
+            (msTag)
             (text))))
       (paragraph
         (p
diff --git a/tree-sitter-usfm/test/corpus/test1_headers.txt b/tree-sitter-usfm/test/corpus/test1_headers.txt
index 0604752c..8e6193f1 100644
--- a/tree-sitter-usfm/test/corpus/test1_headers.txt
+++ b/tree-sitter-usfm/test/corpus/test1_headers.txt
@@ -79,6 +79,7 @@ h marker
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text))))
 
 ================================================================================
@@ -101,9 +102,11 @@ multiple h markers
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text))
     (h
-      (numberedLevelMax3)
+      (hTag
+        (numberedLevelMax3))
       (text))))
 
 ================================================================================
@@ -128,6 +131,7 @@ toc markers under h
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text))
     (tocBlock
       (toc
@@ -192,6 +196,7 @@ toca markers
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text))
     (tocaBlock
       (toca
@@ -228,6 +233,7 @@ toca markers along with toc
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text))
     (tocBlock
       (toc
diff --git a/tree-sitter-usfm/test/corpus/test2_comments.txt b/tree-sitter-usfm/test/corpus/test2_comments.txt
index a66173a0..b5389586 100644
--- a/tree-sitter-usfm/test/corpus/test2_comments.txt
+++ b/tree-sitter-usfm/test/corpus/test2_comments.txt
@@ -33,6 +33,7 @@ rem under one h marker
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (rem
     (text)))
@@ -60,6 +61,7 @@ rem between toc markers and h
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (rem
     (text))
@@ -109,6 +111,7 @@ sts under one h marker
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (sts
     (text)))
@@ -136,6 +139,7 @@ sts between toc markers and h
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (sts
     (text))
@@ -171,6 +175,7 @@ sts and rem maker together in header section
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (sts
     (text))
diff --git a/tree-sitter-usfm/test/corpus/test3_introduction.txt b/tree-sitter-usfm/test/corpus/test3_introduction.txt
index f27e5720..20261273 100644
--- a/tree-sitter-usfm/test/corpus/test3_introduction.txt
+++ b/tree-sitter-usfm/test/corpus/test3_introduction.txt
@@ -22,20 +22,25 @@ intro markers set 1
   (usfm)
   (hBlock
     (h
+      (hTag)
       (text)))
   (mtBlock
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text))
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text)))
   (imtBlock
     (imt
-      (numberedLevelMax4)
+      (imtTag
+        (numberedLevelMax4))
       (text)))
   (isBlock
     (is
+      (isTag)
       (text)))
   (ip
     (text))
@@ -82,36 +87,44 @@ intro markers set 2
     (text))
   (iqBlock
     (iq
-      (numberedLevelMax3)
+      (iqTag
+        (numberedLevelMax3))
       (text))
     (iq
-      (numberedLevelMax3)
+      (iqTag
+        (numberedLevelMax3))
       (text))
     (iq
-      (numberedLevelMax3)
+      (iqTag
+        (numberedLevelMax3))
       (text))
     (iq
-      (numberedLevelMax3)
+      (iqTag
+        (numberedLevelMax3))
       (text)))
   (iot
     (text))
   (io
-    (numberedLevelMax4)
+    (ioTag
+      (numberedLevelMax4))
     (text)
     (ior
       (text)))
   (io
-    (numberedLevelMax4)
+    (ioTag
+      (numberedLevelMax4))
     (text)
     (ior
       (text)))
   (io
-    (numberedLevelMax4)
+    (ioTag
+      (numberedLevelMax4))
     (text)
     (ior
       (text)))
   (io
-    (numberedLevelMax4)
+    (ioTag
+      (numberedLevelMax4))
     (text)))
 
 ================================================================================
@@ -135,12 +148,16 @@ intro markers set 3
     (text))
   (iliBlock
     (ili
+      (iliTag)
       (text))
     (ili
+      (iliTag)
       (text))
     (ili
+      (iliTag)
       (text)))
   (imteBlock
     (imte
+      (imteTag)
       (text)))
   (ie))
diff --git a/tree-sitter-usfm/test/corpus/test4_chapter_verse.txt b/tree-sitter-usfm/test/corpus/test4_chapter_verse.txt
index 7bd02c64..3f9bc243 100644
--- a/tree-sitter-usfm/test/corpus/test4_chapter_verse.txt
+++ b/tree-sitter-usfm/test/corpus/test4_chapter_verse.txt
@@ -70,6 +70,7 @@ cl ca cp markers
       (description)))
   (hBlock
     (h
+      (hTag)
       (text)))
   (chapter
     (cl
diff --git a/tree-sitter-usfm/test/corpus/test5_titles_n_paragraphs.txt b/tree-sitter-usfm/test/corpus/test5_titles_n_paragraphs.txt
index 1f05e7b1..cea44875 100644
--- a/tree-sitter-usfm/test/corpus/test5_titles_n_paragraphs.txt
+++ b/tree-sitter-usfm/test/corpus/test5_titles_n_paragraphs.txt
@@ -42,10 +42,12 @@ headers and titles
       (text)))
   (mtBlock
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text))
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text)))
   (chapter
     (c
@@ -53,12 +55,14 @@ headers and titles
     (title
       (msBlock
         (ms
+          (msTag)
           (text)
           (mr
             (text)))))
     (title
       (sBlock
         (s
+          (sTag)
           (text))))
     (paragraph
       (p
@@ -69,7 +73,8 @@ headers and titles
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text)
           (sr
             (text))
@@ -84,7 +89,8 @@ headers and titles
     (title
       (sdBlock
         (sd
-          (numberedLevelMax4))))
+          (sdTag
+            (numberedLevelMax4)))))
     (paragraph
       (p
         (v
@@ -102,8 +108,10 @@ headers and titles
     (title
       (mteBlock
         (mte
+          (mteTag)
           (text))
         (mte
+          (mteTag)
           (text))))))
 
 ================================================================================
@@ -146,7 +154,8 @@ Simple p
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text)
           (r
             (text)))))
@@ -199,15 +208,18 @@ of the Law say that the Messiah will be the descendant of David?
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -502,7 +514,8 @@ drinks too much! He is even a friend of tax collectors ...
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (paragraph
       (p
@@ -519,6 +532,7 @@ drinks too much! He is even a friend of tax collectors ...
     (paragraph
       (piBlock
         (pi
+          (piTag)
           (verseText
             (text))
           (v
@@ -530,6 +544,7 @@ drinks too much! He is even a friend of tax collectors ...
           (verseText
             (text)))
         (pi
+          (piTag)
           (v
             (verseNumber))
           (verseText
@@ -539,21 +554,25 @@ drinks too much! He is even a friend of tax collectors ...
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -604,7 +623,8 @@ sat down and started teaching them.
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (paragraph
       (p
diff --git a/tree-sitter-usfm/test/corpus/test6_poetry.txt b/tree-sitter-usfm/test/corpus/test6_poetry.txt
index af1fa607..14b850ef 100644
--- a/tree-sitter-usfm/test/corpus/test6_poetry.txt
+++ b/tree-sitter-usfm/test/corpus/test6_poetry.txt
@@ -26,7 +26,8 @@ q#
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (paragraph
       (p
@@ -39,25 +40,30 @@ q#
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))))
 
@@ -89,12 +95,14 @@ qr
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -106,7 +114,8 @@ qr
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -118,7 +127,8 @@ qr
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -157,23 +167,27 @@ qc
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -187,7 +201,8 @@ qc
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -219,7 +234,8 @@ qs
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (title
       (d
@@ -227,23 +243,27 @@ qs
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text))
           (qs
@@ -281,13 +301,15 @@ qa
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (title
@@ -297,13 +319,15 @@ qa
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))))
 
@@ -344,15 +368,18 @@ qac
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -363,31 +390,38 @@ qac
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -395,7 +429,8 @@ qac
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (qac
@@ -404,23 +439,28 @@ qac
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))))
 
@@ -456,15 +496,18 @@ of “The Thirty,” and he called out,
     (poetry
       (qmBlock
         (qm
-          (numberedLevelMax3)
+          (qmTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (qm
-          (numberedLevelMax3)
+          (qmTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (qm
-          (numberedLevelMax3)
+          (qmTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -500,13 +543,15 @@ qd
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -514,17 +559,20 @@ qd
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -574,28 +622,33 @@ b
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -603,27 +656,32 @@ b
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -631,22 +689,26 @@ b
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))))
diff --git a/tree-sitter-usfm/test/corpus/test7_lists_n_tables.txt b/tree-sitter-usfm/test/corpus/test7_lists_n_tables.txt
index e31cf7c9..a38a7c23 100644
--- a/tree-sitter-usfm/test/corpus/test7_lists_n_tables.txt
+++ b/tree-sitter-usfm/test/corpus/test7_lists_n_tables.txt
@@ -33,7 +33,8 @@ lh, li#, lf
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (list
       (lh
@@ -43,55 +44,68 @@ lh, li#, lf
           (text)))
       (liBlock
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (verseText
             (text))))
       (lf
@@ -132,18 +146,23 @@ offerings
     (list
       (liBlock
         (li
+          (liTag)
           (verseText
             (text)))
         (li
+          (liTag)
           (verseText
             (text)))
         (li
+          (liTag)
           (verseText
             (text)))
         (li
+          (liTag)
           (verseText
             (text)))
         (li
+          (liTag)
           (verseText
             (text)))))))
 
@@ -206,7 +225,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
     (list
       (limBlock
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -217,7 +237,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -228,7 +249,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -239,7 +261,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -250,7 +273,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -261,7 +285,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -272,7 +297,8 @@ Bilshan, Mispereth, Bigvai, Nehum and Baanah):
           (verseText
             (text)))
         (lim
-          (numberedLevelMax4)
+          (limTag
+            (numberedLevelMax4))
           (v
             (verseNumber))
           (verseText
@@ -316,7 +342,8 @@ lik, liv#
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text))))
     (list
       (lh
@@ -326,185 +353,224 @@ lik, liv#
           (text)))
       (liBlock
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text)))
         (li
-          (numberedLevelMax4)
+          (liTag
+            (numberedLevelMax4))
           (lik
             (verseText
               (text)))
           (verseText
             (text))
           (liv
-            (numberedLevelMax5)
+            (livTag
+              (numberedLevelMax5))
             (verseText
               (text))
-            (numberedLevelMax5))
+            (livTag
+              (numberedLevelMax5)))
           (verseText
             (text))))
       (lf
@@ -543,62 +609,80 @@ table, with header
     (table
       (tr
         (th
+          (thTag)
           (verseText
             (text)))
         (th
+          (thTag)
           (verseText
             (text)))
         (th
+          (thTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))))))
 
@@ -634,48 +718,62 @@ their groups, under their leaders, as follows:
     (table
       (tr
         (th
+          (thTag)
           (verseText
             (text)))
         (th
+          (thTag)
           (verseText
             (text)))
         (thr
+          (thrTag)
           (verseText
             (text))))
       (tr
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tcr
+          (tcrTag)
           (verseText
             (text))))
       (tr
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tcr
+          (tcrTag)
           (verseText
             (text))))
       (tr
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tc
+          (tcTag)
           (verseText
             (text)))
         (tcr
+          (tcrTag)
           (verseText
             (text))))
       (tr
         (tcr
+          (tcrTag)
           (verseText
             (text)))
         (tcr
+          (tcrTag)
           (verseText
             (text)))))))
diff --git a/tree-sitter-usfm/test/corpus/test8_footnotes_n_crossrefs.txt b/tree-sitter-usfm/test/corpus/test8_footnotes_n_crossrefs.txt
index fcebaf25..18bcbaa3 100644
--- a/tree-sitter-usfm/test/corpus/test8_footnotes_n_crossrefs.txt
+++ b/tree-sitter-usfm/test/corpus/test8_footnotes_n_crossrefs.txt
@@ -21,7 +21,8 @@ manuscripts do not have \fq the Son of God.\f*
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text)
           (r
             (text)))))
@@ -422,7 +423,8 @@ Lk 3.1-18; \xta and \xt Jn 1.19-28 \xta parallel passages.\x*
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text)
           (crossref
             (x
@@ -582,17 +584,20 @@ dead will be raised, never to die again, and we shall all be changed.
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (crossref
@@ -613,7 +618,8 @@ dead will be raised, never to die again, and we shall all be changed.
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph
@@ -675,11 +681,13 @@ than theirs.
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text))
           (crossref
@@ -697,11 +705,13 @@ than theirs.
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text))
           (crossref
diff --git a/tree-sitter-usfm/test/corpus/test9_charactermarkers_n_nesting.txt b/tree-sitter-usfm/test/corpus/test9_charactermarkers_n_nesting.txt
index 85399ee8..d356ff05 100644
--- a/tree-sitter-usfm/test/corpus/test9_charactermarkers_n_nesting.txt
+++ b/tree-sitter-usfm/test/corpus/test9_charactermarkers_n_nesting.txt
@@ -79,19 +79,22 @@ sabachthani?”\tl* which means, “My God, my God, why did you abandon me?”
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (qt
               (text))
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (qt
               (text))
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
@@ -99,13 +102,15 @@ sabachthani?”\tl* which means, “My God, my God, why did you abandon me?”
               (text))
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (qt
               (text))
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (qt
               (text))
@@ -144,7 +149,8 @@ sabachthani?”\tl* which means, “My God, my God, why did you abandon me?”
     (title
       (sBlock
         (s
-          (numberedLevelMax5)
+          (sTag
+            (numberedLevelMax5))
           (text)
           (r
             (text)))))
@@ -185,13 +191,16 @@ and to the ends of the earth” (1.8).
       (description)))
   (mtBlock
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text))
     (mt
-      (numberedLevelMax4)
+      (mtTag
+        (numberedLevelMax4))
       (text)))
   (isBlock
     (is
+      (isTag)
       (text)))
   (ip
     (bk
@@ -240,17 +249,20 @@ dead will be raised, never to die again, and we shall all be changed.
     (poetry
       (qBlock
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (v
             (verseNumber))
           (crossref
@@ -270,7 +282,8 @@ dead will be raised, never to die again, and we shall all be changed.
           (verseText
             (text)))
         (q
-          (numberedLevelMax3)
+          (qTag
+            (numberedLevelMax3))
           (verseText
             (text)))))
     (paragraph