@@ -379,8 +379,8 @@ class XMLProcessor {
379379 /**
380380 * Specifies mode of operation of the parser at any given time.
381381 *
382- * | State | Meaning |
383- * | ----------------|-- ----------------------------------------------------------------------|
382+ * | State | Meaning |
383+ * | ------------------| ----------------------------------------------------------------------|
384384 * | *Ready* | The parser is ready to run. |
385385 * | *Complete* | There is nothing left to parse. |
386386 * | *Incomplete* | The XML ended in the middle of a token; nothing more can be parsed. |
@@ -735,13 +735,14 @@ class XMLProcessor {
735735 protected $ parser_context = self ::IN_PROLOG_CONTEXT ;
736736
737737 /**
738- * Tracks open elements while scanning XML.
739- *
740- * @since WP_VERSION
741- *
742- * @var XMLStackOfOpenElements
738+ * Top-level namespaces for the currently parsed document.
743739 */
744- private $ stack_of_open_elements ;
740+ private $ document_namespaces ;
741+
742+ /**
743+ * Tracks open elements and their namespaces while scanning XML.
744+ */
745+ private $ stack_of_open_elements = [];
745746
746747 public static function create_from_string ( $ xml , $ cursor = null , $ known_definite_encoding = 'UTF-8 ' , $ document_namespaces = array () ) {
747748 $ processor = static ::create_for_streaming ( $ xml , $ cursor , $ known_definite_encoding , $ document_namespaces );
@@ -781,7 +782,7 @@ public static function create_for_streaming( $xml = '', $cursor = null, $known_d
781782 */
782783 public function get_reentrancy_cursor () {
783784 $ stack_of_open_elements = [];
784- foreach ( $ this ->stack_of_open_elements -> get_items () as $ element ) {
785+ foreach ( $ this ->stack_of_open_elements as $ element ) {
785786 $ stack_of_open_elements [] = $ element ->to_array ();
786787 }
787788
@@ -792,7 +793,8 @@ public function get_reentrancy_cursor() {
792793 'upstream_bytes_forgotten ' => $ this ->upstream_bytes_forgotten ,
793794 'parser_context ' => $ this ->parser_context ,
794795 'stack_of_open_elements ' => $ stack_of_open_elements ,
795- 'expecting_more_input ' => $ this ->expecting_more_input
796+ 'expecting_more_input ' => $ this ->expecting_more_input ,
797+ 'document_namespaces ' => $ this ->document_namespaces
796798 )
797799 )
798800 );
@@ -837,10 +839,11 @@ protected function initialize_from_cursor( $cursor ) {
837839 // Assume the input stream will start from the last known byte offset.
838840 $ this ->bytes_already_parsed = 0 ;
839841 $ this ->upstream_bytes_forgotten = $ cursor ['upstream_bytes_forgotten ' ];
840- $ this ->stack_of_open_elements = new XMLStackOfOpenElements () ;
842+ $ this ->stack_of_open_elements = [] ;
841843 foreach ( $ cursor ['stack_of_open_elements ' ] as $ element ) {
842- $ this ->stack_of_open_elements -> push ( XMLElement::from_array ( $ element ) );
844+ array_push ( $ this ->stack_of_open_elements , XMLElement::from_array ( $ element ) );
843845 }
846+ $ this ->document_namespaces = $ cursor ['document_namespaces ' ];
844847 $ this ->parser_context = $ cursor ['parser_context ' ];
845848 $ this ->expecting_more_input = $ cursor ['expecting_more_input ' ];
846849
@@ -876,7 +879,15 @@ protected function __construct( $xml, $document_namespaces=[], $use_the_static_c
876879 );
877880 }
878881 $ this ->xml = $ xml ;
879- $ this ->stack_of_open_elements = new XMLStackOfOpenElements ($ document_namespaces );
882+ $ this ->document_namespaces = array_merge (
883+ $ document_namespaces ,
884+ // These initial namespaces cannot be overridden.
885+ array (
886+ 'xml ' => 'http://www.w3.org/XML/1998/namespace ' , // Predefined, cannot be unbound or changed
887+ 'xmlns ' => 'http://www.w3.org/2000/xmlns/ ' , // Reserved for xmlns attributes, not a real namespace for elements/attributes
888+ '' => '' , // Default namespace is initially empty (no namespace)
889+ )
890+ );
880891 }
881892
882893 /**
@@ -1082,7 +1093,7 @@ protected function parse_next_token() {
10821093 /**
10831094 * By default, inherit all namespaces from the parent element.
10841095 */
1085- $ namespaces = $ this ->stack_of_open_elements -> get_namespaces_in_scope ();
1096+ $ namespaces = $ this ->get_namespaces_in_scope ();
10861097 foreach ( $ this ->qualified_attributes as $ attribute ) {
10871098 /**
10881099 * xmlns attribute is the default namespace
@@ -1251,6 +1262,69 @@ protected function parse_next_token() {
12511262 return true ;
12521263 }
12531264
1265+ private function get_namespaces_in_scope () {
1266+ $ top = $ this ->top_element ();
1267+ if ( null === $ top ) {
1268+ // Namespaces defined by default in every XML document.
1269+ return $ this ->document_namespaces ;
1270+ }
1271+ return $ top ->namespaces_in_scope ;
1272+ }
1273+
1274+ /**
1275+ * Returns the namespace prefix of the matched tag or, when the $namespace
1276+ * argument is given, the prefix of the requested fully-qualified namespace .
1277+ *
1278+ * Examples:
1279+ *
1280+ * $p = new XMLProcessor( '<wp:content xmlns:xhtml="http://www.w3.org/1999/xhtml">Test</wp:content>' );
1281+ * $p->next_tag() === true;
1282+ * $p->get_namespace_prefix() === 'xhtml';
1283+ *
1284+ * $p = new XMLProcessor( '
1285+ * <wp:content
1286+ * xmlns:xhtml="http://www.w3.org/1999/xhtml"
1287+ * xmlns:wp="http://wordpress.org/export/1.2/"
1288+ * >
1289+ * Test
1290+ * </wp:content>
1291+ * ' );
1292+ * $p->next_tag() === true;
1293+ * $p->get_namespace_prefix('http://wordpress.org/export/1.2/') === 'wp';
1294+ *
1295+ * @internal
1296+ * @param string|null $namespace Fully-qualified namespace to return the prefix for.
1297+ * @return string|null The namespace prefix of the matched tag, or null if not available.
1298+ */
1299+ private function get_namespace_prefix ($ namespace =null ) {
1300+ if ( null === $ namespace ) {
1301+ if ( self ::STATE_MATCHED_TAG !== $ this ->parser_state ) {
1302+ return null ;
1303+ }
1304+ return $ this ->element ->namespace_prefix ;
1305+ } else {
1306+ $ namespaces_in_scope = $ this ->get_namespaces_in_scope ();
1307+ foreach ($ namespaces_in_scope as $ prefix => $ uri ) {
1308+ if ($ uri === $ namespace ) {
1309+ return $ prefix ;
1310+ }
1311+ }
1312+ return false ;
1313+ }
1314+ }
1315+
1316+ /**
1317+ * Returns the top XMLElement on the stack without removing it.
1318+ *
1319+ * @return XMLElement|null Returns the top element, or null if stack is empty.
1320+ */
1321+ private function top_element () {
1322+ if ( empty ( $ this ->stack_of_open_elements ) ) {
1323+ return null ;
1324+ }
1325+ return $ this ->stack_of_open_elements [ count ( $ this ->stack_of_open_elements ) - 1 ];
1326+ }
1327+
12541328 /**
12551329 * Whether the processor paused because the input XML document ended
12561330 * in the middle of a syntax element, such as in the middle of a tag.
@@ -2996,25 +3070,6 @@ public function get_tag_name_with_namespace() {
29963070 return '{ ' . $ namespace . '} ' . $ this ->get_tag_local_name ();
29973071 }
29983072
2999- /**
3000- * Returns the namespace prefix of the matched tag.
3001- *
3002- * Example:
3003- *
3004- * $p = new XMLProcessor( '<content xmlns:wp="http://www.w3.org/1999/xhtml">Test</content>' );
3005- * $p->next_tag() === true;
3006- * $p->get_namespace_prefix() === 'wp';
3007- *
3008- * @return string|null The namespace prefix of the matched tag, or null if not available.
3009- */
3010- public function get_namespace_prefix () {
3011- if ( self ::STATE_MATCHED_TAG !== $ this ->parser_state ) {
3012- return null ;
3013- }
3014-
3015- return $ this ->element ->namespace_prefix ;
3016- }
3017-
30183073 /**
30193074 * Returns the namespace reference of the matched tag.
30203075 *
@@ -3035,10 +3090,6 @@ public function get_namespace() {
30353090 return $ this ->element ->namespace ;
30363091 }
30373092
3038- public function get_namespaces_in_scope () {
3039- return $ this ->stack_of_open_elements ->get_namespaces_in_scope ();
3040- }
3041-
30423093 /**
30433094 * Returns the name from the DOCTYPE declaration.
30443095 *
@@ -3433,7 +3484,7 @@ public function set_attribute( $namespace, $local_name, $value ) {
34333484 $ value = htmlspecialchars ( $ value , ENT_XML1 , 'UTF-8 ' );
34343485
34353486 if ($ namespace !== '' ) {
3436- $ prefix = $ this ->stack_of_open_elements -> get_namespace_prefix ($ namespace );
3487+ $ prefix = $ this ->get_namespace_prefix ($ namespace );
34373488 if (false === $ prefix ) {
34383489 $ this ->bail (
34393490 __ ( 'The namespace "%1$s" is not in the current element \'s scope. ' ),
@@ -3680,9 +3731,10 @@ private function step( $node_to_process = self::PROCESS_NEXT_NODE ) {
36803731
36813732 if ( self ::PROCESS_NEXT_NODE === $ node_to_process ) {
36823733 if ( $ this ->is_empty_element () ) {
3683- $ this ->stack_of_open_elements -> pop ( );
3734+ array_pop ( $ this ->stack_of_open_elements );
36843735 }
36853736 }
3737+
36863738
36873739 try {
36883740 switch ( $ this ->parser_context ) {
@@ -3798,15 +3850,15 @@ private function step_in_element( $node_to_process = self::PROCESS_NEXT_NODE ) {
37983850 // Update the stack of open elements
37993851 $ tag_qname = $ this ->get_tag_name_qualified ();
38003852 if ( $ this ->is_tag_closer () ) {
3801- if (!$ this ->stack_of_open_elements -> count ( )) {
3853+ if (!count ( $ this ->stack_of_open_elements )) {
38023854 $ this ->bail (
38033855 __ ( 'The closing tag "%1$s" did not match the opening tag "%2$s". ' ),
38043856 $ tag_qname ,
38053857 $ tag_qname
38063858 );
38073859 return false ;
38083860 }
3809- $ this ->element = $ this ->stack_of_open_elements -> pop ( );
3861+ $ this ->element = array_pop ( $ this ->stack_of_open_elements );
38103862 $ popped_qname = $ this ->element ->qualified_name ;
38113863 if ( $ popped_qname !== $ tag_qname ) {
38123864 $ this ->bail (
@@ -3819,12 +3871,12 @@ private function step_in_element( $node_to_process = self::PROCESS_NEXT_NODE ) {
38193871 self ::ERROR_SYNTAX
38203872 );
38213873 }
3822- if ( $ this ->stack_of_open_elements -> count ( ) === 0 ) {
3874+ if ( count ( $ this ->stack_of_open_elements ) === 0 ) {
38233875 $ this ->parser_context = self ::IN_MISC_CONTEXT ;
38243876 }
38253877 } else {
3826- $ this ->stack_of_open_elements -> push ( $ this ->element );
3827- $ this ->element = $ this ->stack_of_open_elements -> top ();
3878+ array_push ( $ this ->stack_of_open_elements , $ this ->element );
3879+ $ this ->element = $ this ->top_element ();
38283880 }
38293881
38303882 return true ;
@@ -3908,7 +3960,7 @@ private function step_in_misc( $node_to_process = self::PROCESS_NEXT_NODE ) {
39083960 public function get_breadcrumbs () {
39093961 return array_map ( function ( $ element ) {
39103962 return array ( $ element ->namespace , $ element ->local_name );
3911- }, $ this ->stack_of_open_elements -> get_items () );
3963+ }, $ this ->stack_of_open_elements );
39123964 }
39133965
39143966 /**
@@ -3950,7 +4002,7 @@ public function matches_breadcrumbs( $breadcrumbs ) {
39504002 return false ;
39514003 }
39524004
3953- $ open_elements = $ this ->stack_of_open_elements -> get_items () ;
4005+ $ open_elements = $ this ->stack_of_open_elements ;
39544006 $ crumb_count = count ( $ breadcrumbs );
39554007 $ elem_count = count ( $ open_elements );
39564008
@@ -4011,7 +4063,7 @@ public function matches_breadcrumbs( $breadcrumbs ) {
40114063 *
40124064 */
40134065 public function get_current_depth () {
4014- return $ this ->stack_of_open_elements -> count ( );
4066+ return count ( $ this ->stack_of_open_elements );
40154067 }
40164068
40174069 /**
0 commit comments