diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 94b46a6..752eadf 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4208,9 +4208,11 @@ a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
    <para>
     The <type>xml</type> type can store well-formed
     <quote>documents</quote>, as defined by the XML standard, as well
-    as <quote>content</quote> fragments, which are defined by the
-    production <literal>XMLDecl? content</literal> in the XML
-    standard.  Roughly, this means that content fragments can have
+    as <quote>content</quote> fragments, which are defined by reference to the
+    more permissive
+    <ulink url="https://www.w3.org/TR/2010/REC-xpath-datamodel-20101214/#DocumentNode"><quote>document node</quote></ulink>
+    of the XQuery and XPath data model.
+    Roughly, this means that content fragments can have
     more than one top-level element or character node.  The expression
     <literal><replaceable>xmlvalue</replaceable> IS DOCUMENT</literal>
     can be used to evaluate whether a particular <type>xml</type>
@@ -4282,24 +4284,9 @@ SET XML OPTION { DOCUMENT | CONTENT };
 SET xmloption TO { DOCUMENT | CONTENT };
 </synopsis>
     The default is <literal>CONTENT</literal>, so all forms of XML
-    data are allowed except as noted below.
+    data are allowed.
    </para>
 
-   <note>
-    <para>
-     In the SQL:2006 and later standard, the <literal>CONTENT</literal> form
-     is a proper superset of the <literal>DOCUMENT</literal> form, and so the
-     default XML option setting would allow casting to XML from character
-     strings in either form. <productname>PostgreSQL</productname>, however,
-     uses the SQL:2003 definition in which <literal>CONTENT</literal> form
-     cannot contain a document type declaration. Therefore, there is no one
-     setting of the XML option that will allow casting to XML from every valid
-     character string. The default will work almost always, but for a document
-     with a DTD, it will be necessary to change the XML option or
-     use <literal>XMLPARSE</literal> specifying <literal>DOCUMENT</literal>.
-    </para>
-   </note>
-
    </sect2>
 
    <sect2>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index e947613..32908c1 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -569,7 +569,7 @@ X056	Advanced table mapping: data mapping			YES
 X057	Advanced table mapping: metadata mapping			YES	
 X058	Advanced table mapping: base64 encoding of binary strings			YES	
 X059	Advanced table mapping: hex encoding of binary strings			YES	
-X060	XMLParse: character string input and CONTENT option			YES	uses SQL:2003 definition, so a document with a DTD cannot be parsed as CONTENT
+X060	XMLParse: character string input and CONTENT option			YES	
 X061	XMLParse: character string input and DOCUMENT option			YES	
 X065	XMLParse: BLOB input and CONTENT option			NO	
 X066	XMLParse: BLOB input and DOCUMENT option			NO	
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 28b3eaa..d326140 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -141,6 +141,7 @@ static int parse_xml_decl(const xmlChar *str, size_t *lenp,
 			   xmlChar **version, xmlChar **encoding, int *standalone);
 static bool print_xml_decl(StringInfo buf, const xmlChar *version,
 			   pg_enc encoding, int standalone);
+static bool xml_doctype_in_content(const xmlChar *str);
 static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
 		  bool preserve_whitespace, int encoding);
 static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
@@ -1243,8 +1244,19 @@ parse_xml_decl(const xmlChar *str, size_t *lenp,
 	if (xmlStrncmp(p, (xmlChar *) "<?xml", 5) != 0)
 		goto finished;
 
-	/* if next char is name char, it's a PI like <?xml-stylesheet ...?> */
-	utf8len = strlen((const char *) (p + 5));
+	/*
+	 * If next char is name char, it's a PI like <?xml-stylesheet ...?> rather
+	 * than an XMLDecl, so we have done what we came to do and found no XMLDecl.
+	 *
+	 * Use strnlen here because there is no need to go off counting all the rest
+	 * of the input just to find out if there are enough bytes to decode one
+	 * UTF-8 char. 6 is conservative: UTF-8 finally settled on 4 bytes as the
+	 * longest encoding, but could some input come from a source that (a) saw
+	 * the original drafts with up to 6 and (b) produces overlong encodings?
+	 * Hardly likely, but it costs nearly nothing to accommodate it, while using
+	 * 4 could be a change of behavior from when strlen was here.
+	 */
+	utf8len = strnlen((const char *) (p + 5), 6);
 	utf8char = xmlGetUTF8Char(p + 5, &utf8len);
 	if (PG_XMLISNAMECHAR(utf8char))
 		goto finished;
@@ -1415,6 +1427,133 @@ print_xml_decl(StringInfo buf, const xmlChar *version,
 		return false;
 }
 
+/*
+ * Test whether an input that is to be parsed as CONTENT contains a DTD.
+ *
+ * The SQL/XML:2003 definition of CONTENT ("XMLDecl? content") is not satisfied
+ * by a document with a DTD, which is a bit of a wart, as it means the CONTENT
+ * type is not a proper superset of DOCUMENT. SQL/XML:2006 and later fix that,
+ * by redefining content with reference to the "more permissive" Document Node
+ * of the XQuery/XPath Data Model, such that any DOCUMENT value is indeed also a
+ * CONTENT value. That definition is more useful, as CONTENT becomes usable for
+ * parsing input of unknown form (think pg_restore).
+ *
+ * As used below in parse_xml when parsing for CONTENT, libxml does not give us
+ * the 2006+ behavior, but only the 2003; it will choke if the input has a DTD.
+ * But without the full effort of bringing our whole implementation along to
+ * 2006+, we can provide the 2006+ definition of CONTENT easily enough, by
+ * detecting this case first and simply doing the parse as DOCUMENT.
+ *
+ * A DTD can be found arbitrarily far in, but that would be a contrived case; it
+ * will ordinarily start within a few dozen characters. We only need to see the
+ * start. The only things that can precede it are an XMLDecl (here, the caller
+ * will have called parse_xml_decl already), whitespace, comments, and
+ * processing instructions. This function need only return true if it sees a
+ * valid sequence of such things leading to <!DOCTYPE. It can simply return
+ * false in any other cases, including malformed input; that will mean the input
+ * gets parsed as CONTENT as originally planned, with libxml reporting any
+ * errors.
+ *
+ * By taking care to return false for malformed input, this ensures the user
+ * gets whatever error would be reported in a parse for CONTENT, as expected,
+ * rather than being surprised for errors about DOCUMENT form (except in the
+ * specific case we are here to recognize, where the input has a DTD and is
+ * therefore begging for DOCUMENT treatment).
+ *
+ * This is only to be called from xml_parse, when pg_xml_init has already
+ * been called. That the input is UTF-8 is heavily relied on.
+ */
+static bool
+xml_doctype_in_content(const xmlChar *str)
+{
+	const xmlChar *p = str;
+	const xmlChar *e;
+	const xmlChar *sp;
+	int			   utf8char;
+	int			   utf8len;
+
+	for ( ;; )
+	{
+		SKIP_XML_SPACE(p);
+
+		if ( '<' != *p )
+			return false;
+
+		++ p; /* point to char following < */
+
+		if ( '!' == *p )
+		{
+			++ p; /* point to char following <! */
+			if ( 0 == xmlStrncmp(p, (xmlChar *) "DOCTYPE", 7) )
+				return true;
+			if ( 0 != xmlStrncmp(p, (xmlChar *) "--", 2) )
+				return false;
+			/* consume remainder of a comment: find -- and a > must follow */
+			p = xmlStrstr(p + 2, (xmlChar *) "--");
+			if ( !p  ||  '>' != p[2] )
+				return false; /* didn't find -- or > didn't follow */
+			p += 3; /* point to char following --> */
+			continue;
+		}
+
+		if ( '?' != *p ) /* a PI <?target something?> is the only choice left */
+			return false;
+
+		++ p; /* point to char following <? */
+
+		/* The string ?> is forbidden within a PI; it can only mean the end. */
+		e = xmlStrstr(p, (xmlChar *) "?>");
+		if ( !e )
+			return false;
+
+		/*
+		 * The target is from p to the first blank (or end of PI if no blank).
+		 * The characters accepted by xmlIsBlank are all 7-bit; it will not be
+		 * confounded by any multibyte UTF-8 characters, though it will end up
+		 * seeing each byte individually.
+		 */
+		for ( sp = p; sp < e; ++ sp )
+			if ( xmlIsBlank_ch(*sp) )
+				break;
+
+		if ( sp == p )
+			return false; /* there should have been at least one non-blank */
+
+		/* The target must not be, case-insensitively, "xml". */
+		if ( sp - p == 3  &&  0 == xmlStrncasecmp(p, (xmlChar *) "xml", 3) )
+			return false;
+
+		utf8len = sp - p;
+		utf8char = xmlGetUTF8Char(p, &utf8len);
+		if ( ! PG_XMLISNAMECHAR(utf8char) )
+			return false;
+		/* certain chars are excluded as the FIRST name char */
+		if ( '-' == utf8char || '.' == utf8char || 0xB7 == utf8char ||
+			('0' <= utf8char && utf8char <= '9') ||
+			( 0x300 <= utf8char && utf8char <= 0x36F ) ||
+			0x203F == utf8char || 0x2040 == utf8char )
+			return false;
+
+		p += utf8len;
+
+		while ( p < sp ) /* the rest can be anything PG_XMLISNAMECHAR accepts */
+		{
+			utf8len = sp - p;
+			utf8char = xmlGetUTF8Char(p, &utf8len);
+			if ( ! PG_XMLISNAMECHAR(utf8char) )
+				return false;
+			p += utf8len;
+		}
+
+		/*
+		 * The target checks out as a NAME. There are no constraints on the rest
+		 * of the content in a PI (besides that it can't be ?> relied on above),
+		 * so it's good. Point at the char following the PI's ending ?>
+		 */
+		p = e + 2;
+	}
+}
+
 
 /*
  * Convert a C string to XML internal representation
@@ -1433,6 +1572,9 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 	xmlChar    *string;
 	xmlChar    *utf8string;
 	PgXmlErrorContext *xmlerrcxt;
+	int			errcode_document_parse = ERRCODE_INVALID_XML_DOCUMENT;
+	char const *errmsg_document_parse = "invalid XML document";
+	char const *errmsg_content_parse = "invalid XML content";
 	volatile xmlParserCtxtPtr ctxt = NULL;
 	volatile xmlDocPtr doc = NULL;
 
@@ -1457,6 +1599,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
 						"could not allocate parser context");
 
+try_other_xmloption:
 		if (xmloption_arg == XMLOPTION_DOCUMENT)
 		{
 			/*
@@ -1472,8 +1615,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 								 XML_PARSE_NOENT | XML_PARSE_DTDATTR
 								 | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS));
 			if (doc == NULL || xmlerrcxt->err_occurred)
-				xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
-							"invalid XML document");
+				xml_ereport(xmlerrcxt, ERROR, errcode_document_parse,
+							errmsg_document_parse);
 		}
 		else
 		{
@@ -1489,6 +1632,21 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 									"invalid XML content: invalid XML declaration",
 									res_code);
 
+			/*
+			 * The way we use libxml below to parse CONTENT will not accept the
+			 * case (not allowed in SQL/XML:2003 but allowed in 2006+) where the
+			 * content is, in fact, a document with a DTD. By detecting that
+			 * case here and parsing as DOCUMENT instead, we provide the 2006+
+			 * behavior.
+			 */
+			if (xml_doctype_in_content(utf8string + count))
+			{
+				xmloption_arg = XMLOPTION_DOCUMENT;
+				errcode_document_parse = ERRCODE_INVALID_XML_CONTENT;
+				errmsg_document_parse = errmsg_content_parse;
+				goto try_other_xmloption;
+			}
+
 			doc = xmlNewDoc(version);
 			Assert(doc->encoding == NULL);
 			doc->encoding = xmlStrdup((const xmlChar *) "UTF-8");
@@ -1501,7 +1659,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
 													   utf8string + count, NULL);
 				if (res_code != 0 || xmlerrcxt->err_occurred)
 					xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_CONTENT,
-								"invalid XML content");
+								errmsg_content_parse);
 			}
 		}
 	}
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 2085fa0..0aae600 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -532,6 +532,13 @@ LINE 1: EXECUTE foo ('bad');
 DETAIL:  line 1: Start tag expected, '<' not found
 bad
 ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+                ^
 SET XML OPTION CONTENT;
 EXECUTE foo ('<bar/>');
   xmlconcat   
@@ -545,6 +552,45 @@ EXECUTE foo ('good');
  <foo/>good
 (1 row)
 
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+                                xml                                 
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+             xml              
+------------------------------
+  <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+       xml        
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+                 ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+                    ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+                ^
 -- Test backwards parsing
 CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
 CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 7f86696..d1a03b5 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -429,11 +429,53 @@ EXECUTE foo ('<bar/>');
 ERROR:  prepared statement "foo" does not exist
 EXECUTE foo ('bad');
 ERROR:  prepared statement "foo" does not exist
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
 SET XML OPTION CONTENT;
 EXECUTE foo ('<bar/>');
 ERROR:  prepared statement "foo" does not exist
 EXECUTE foo ('good');
 ERROR:  prepared statement "foo" does not exist
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/>';
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  unsupported XML feature
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
 -- Test backwards parsing
 CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
 CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 510b09b..9756b6e 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -512,6 +512,13 @@ LINE 1: EXECUTE foo ('bad');
 DETAIL:  line 1: Start tag expected, '<' not found
 bad
 ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  invalid XML document
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+                ^
 SET XML OPTION CONTENT;
 EXECUTE foo ('<bar/>');
   xmlconcat   
@@ -525,6 +532,45 @@ EXECUTE foo ('good');
  <foo/>good
 (1 row)
 
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+                                xml                                 
+--------------------------------------------------------------------
+ <!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+             xml              
+------------------------------
+  <!-- hi--> <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!DOCTYPE a><a/>';
+       xml        
+------------------
+ <!DOCTYPE a><a/>
+(1 row)
+
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  line 1: StartTag: invalid element name
+<!-- hi--> oops <!DOCTYPE a><a/>
+                 ^
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+                   ^
+DETAIL:  line 1: StartTag: invalid element name
+<!-- hi--> <oops/> <!DOCTYPE a><a/>
+                    ^
+SELECT xml '<!DOCTYPE a><a/><b/>';
+ERROR:  invalid XML content
+LINE 1: SELECT xml '<!DOCTYPE a><a/><b/>';
+                   ^
+DETAIL:  line 1: Extra content at the end of the document
+<!DOCTYPE a><a/><b/>
+                ^
 -- Test backwards parsing
 CREATE VIEW xmlview1 AS SELECT xmlcomment('test');
 CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you');
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index 8057a46..71431d8 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -149,10 +149,17 @@ PREPARE foo (xml) AS SELECT xmlconcat('<foo/>', $1);
 SET XML OPTION DOCUMENT;
 EXECUTE foo ('<bar/>');
 EXECUTE foo ('bad');
+SELECT xml '<!DOCTYPE a><a/><b/>';
 
 SET XML OPTION CONTENT;
 EXECUTE foo ('<bar/>');
 EXECUTE foo ('good');
+SELECT xml '<!-- in SQL:2006+ a doc is content too--> <?y z?> <!DOCTYPE a><a/>';
+SELECT xml '<?xml version="1.0"?> <!-- hi--> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> oops <!DOCTYPE a><a/>';
+SELECT xml '<!-- hi--> <oops/> <!DOCTYPE a><a/>';
+SELECT xml '<!DOCTYPE a><a/><b/>';
 
 
 -- Test backwards parsing
