diff --git a/lib/saxes.js b/lib/saxes.js
index c8f9369c..3e26a859 100644
--- a/lib/saxes.js
+++ b/lib/saxes.js
@@ -24,14 +24,21 @@ const XML_ENTITIES = {
const S_INITIAL = "sInitial"; // initial state
const S_BEGIN_WHITESPACE = "sBeginWhitespace"; // leading whitespace
+const S_DOCTYPE = "sDoctype"; // will be recorded as
+ // a comment of " blah -- bloo "
+ this.state = S_DTD_COMMENT;
+ }
+ }
+
+ /** @private */
+ sDTDPI() {
+ if (this.captureToChar(QUESTION, "doctype")) {
+ this.doctype += "?";
+ this.state = S_DTD_PI_ENDING;
+ }
+ }
+
+ /** @private */
+ sDTDPIEnding() {
+ const c = this.getCode();
+ this.doctype += String.fromCodePoint(c);
+ if (c === GREATER) {
+ this.state = S_DTD;
+ }
+ }
+
/** @private */
sComment() {
if (this.captureToChar(MINUS, "comment")) {
diff --git a/test/dtd.js b/test/dtd.js
new file mode 100644
index 00000000..47b8533e
--- /dev/null
+++ b/test/dtd.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { test } = require(".");
+
+describe("dtd", () => {
+ test({
+ name: "DTD with comment containing a quote",
+ xml: `\
+
+
+]>
+`,
+ expect: [
+ ["text", "\n"],
+ ["doctype", ` root [
+
+]`],
+ ["text", "\n"],
+ ["opentagstart", { name: "root", attributes: {} }],
+ ["opentag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ["closetag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ],
+ });
+
+ test({
+ name: "DTD with processing instruction containing a quote",
+ xml: `\
+
+
+]>
+`,
+ expect: [
+ ["text", "\n"],
+ ["doctype", ` root [
+ I'm a test. ?>
+]`],
+ ["text", "\n"],
+ ["opentagstart", { name: "root", attributes: {} }],
+ ["opentag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ["closetag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ],
+ });
+
+ test({
+ name: "DTD with ]> in a string",
+ xml: `\
+
+">
+]>
+`,
+ expect: [
+ ["text", "\n"],
+ ["doctype", ` root [
+">
+]`],
+ ["text", "\n"],
+ ["opentagstart", { name: "root", attributes: {} }],
+ ["opentag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ["closetag", { name: "root", attributes: {}, isSelfClosing: true }],
+ ],
+ });
+});