@@ -168,6 +168,7 @@ def initialize( source )
168168 @entity_expansion_limit = Security . entity_expansion_limit
169169 @entity_expansion_text_limit = Security . entity_expansion_text_limit
170170 @source . ensure_buffer
171+ @version = nil
171172 end
172173
173174 def add_listener ( listener )
@@ -735,37 +736,93 @@ def process_comment
735736
736737 def process_instruction
737738 name = parse_name ( "Malformed XML: Invalid processing instruction node" )
738- if @source . skip_spaces?
739- match_data = @source . match ( /(.*?)\? >/um , true )
740- unless match_data
741- raise ParseException . new ( "Malformed XML: Unclosed processing instruction" , @source )
739+ if name == "xml"
740+ xml_declaration
741+ else # PITarget
742+ if @source . skip_spaces? # e.g. <?name content?>
743+ start_position = @source . position
744+ content = @source . read_until ( "?>" )
745+ unless content . chomp! ( "?>" )
746+ @source . position = start_position
747+ raise ParseException . new ( "Malformed XML: Unclosed processing instruction: <#{ name } >" , @source )
748+ end
749+ else # e.g. <?name?>
750+ content = nil
751+ unless @source . match? ( "?>" , true )
752+ raise ParseException . new ( "Malformed XML: Unclosed processing instruction: <#{ name } >" , @source )
753+ end
742754 end
743- content = match_data [ 1 ]
744- else
745- content = nil
755+ [ :processing_instruction , name , content ]
756+ end
757+ end
758+
759+ def xml_declaration
760+ unless @version . nil?
761+ raise ParseException . new ( "Malformed XML: XML declaration is duplicated" , @source )
762+ end
763+ if @document_status
764+ raise ParseException . new ( "Malformed XML: XML declaration is not at the start" , @source )
765+ end
766+ unless @source . skip_spaces?
767+ raise ParseException . new ( "Malformed XML: XML declaration is missing spaces" , @source )
768+ end
769+ unless @source . match? ( "version" , true )
770+ raise ParseException . new ( "Malformed XML: XML declaration is missing version" , @source )
771+ end
772+ @version = get_attribute ( "xml" )
773+ unless @source . skip_spaces?
746774 unless @source . match? ( "?>" , true )
747- raise ParseException . new ( "Malformed XML: Unclosed processing instruction " , @source )
775+ raise ParseException . new ( "Malformed XML: Unclosed XML declaration " , @source )
748776 end
749- end
750- if name == "xml"
751- if @document_status
752- raise ParseException . new ( "Malformed XML: XML declaration is not at the start" , @source )
777+ if /\A UTF-16(?:BE|LE)\z /i =~ @source . encoding
778+ encoding = "UTF-16"
753779 end
754- version = VERSION . match ( content )
755- version = version [ 1 ] unless version . nil?
756- encoding = ENCODING . match ( content )
757- encoding = encoding [ 1 ] unless encoding . nil?
758- if need_source_encoding_update? ( encoding )
759- @source . encoding = encoding
780+ return [ :xmldecl , @version , encoding , nil ] # e.g. <?xml version="1.0"?>
781+ end
782+
783+ if @source . match? ( "encoding" , true )
784+ encoding = get_attribute ( "xml" )
785+ unless @source . skip_spaces?
786+ unless @source . match? ( "?>" , true )
787+ raise ParseException . new ( "Malformed XML: Unclosed XML declaration" , @source )
788+ end
789+ if need_source_encoding_update? ( encoding )
790+ @source . encoding = encoding
791+ end
792+
793+ if encoding . nil? and /\A UTF-16(?:BE|LE)\z /i =~ @source . encoding
794+ encoding = "UTF-16"
795+ end
796+ return [ :xmldecl , @version , encoding , nil ] # e.g. <?xml version="1.1" encoding="UTF-8"?>
760797 end
761- if encoding . nil? and /\A UTF-16(?:BE|LE)\z /i =~ @source . encoding
762- encoding = "UTF-16"
798+ end
799+
800+ if @source . match? ( "standalone" , true )
801+ standalone = get_attribute ( "xml" )
802+ case standalone
803+ when "yes" , "no"
804+ else
805+ raise ParseException . new ( "Malformed XML: XML declaration standalone is not yes or no : <#{ standalone } >" , @source )
763806 end
764- standalone = STANDALONE . match ( content )
765- standalone = standalone [ 1 ] unless standalone . nil?
766- return [ :xmldecl , version , encoding , standalone ]
767807 end
768- [ :processing_instruction , name , content ]
808+ @source . match? ( /\s */um , true ) # skip spaces
809+ unless @source . match? ( "?>" , true )
810+ raise ParseException . new ( "Malformed XML: Unclosed XML declaration" , @source )
811+ end
812+
813+ if need_source_encoding_update? ( encoding )
814+ @source . encoding = encoding
815+ end
816+
817+ if encoding . nil? and /\A UTF-16(?:BE|LE)\z /i =~ @source . encoding
818+ encoding = "UTF-16"
819+ end
820+
821+ # e.g. <?xml version="1.0" ?>
822+ # <?xml version="1.1" encoding="UTF-8" ?>
823+ # <?xml version="1.1" standalone="yes"?>
824+ # <?xml version="1.1" encoding="UTF-8" standalone="yes" ?>
825+ [ :xmldecl , @version , encoding , standalone ]
769826 end
770827
771828 if StringScanner ::Version < "3.1.1"
@@ -787,6 +844,25 @@ def scan_quote
787844 end
788845 end
789846
847+ def get_attribute ( name )
848+ unless @source . match? ( /\s *=\s */um , true )
849+ message = "Missing attribute equal: <#{ name } >"
850+ raise REXML ::ParseException . new ( message , @source )
851+ end
852+ unless quote = scan_quote
853+ message = "Missing attribute value start quote: <#{ name } >"
854+ raise REXML ::ParseException . new ( message , @source )
855+ end
856+ start_position = @source . position
857+ value = @source . read_until ( quote )
858+ unless value . chomp! ( quote )
859+ @source . position = start_position
860+ message = "Missing attribute value end quote: <#{ name } >: <#{ quote } >"
861+ raise REXML ::ParseException . new ( message , @source )
862+ end
863+ value
864+ end
865+
790866 def parse_attributes ( prefixes )
791867 attributes = { }
792868 expanded_names = { }
@@ -801,22 +877,7 @@ def parse_attributes(prefixes)
801877 name = match [ 1 ]
802878 prefix = match [ 2 ]
803879 local_part = match [ 3 ]
804-
805- unless @source . match? ( /\s *=\s */um , true )
806- message = "Missing attribute equal: <#{ name } >"
807- raise REXML ::ParseException . new ( message , @source )
808- end
809- unless quote = scan_quote
810- message = "Missing attribute value start quote: <#{ name } >"
811- raise REXML ::ParseException . new ( message , @source )
812- end
813- start_position = @source . position
814- value = @source . read_until ( quote )
815- unless value . chomp! ( quote )
816- @source . position = start_position
817- message = "Missing attribute value end quote: <#{ name } >: <#{ quote } >"
818- raise REXML ::ParseException . new ( message , @source )
819- end
880+ value = get_attribute ( name )
820881 @source . match? ( /\s */um , true )
821882 if prefix == "xmlns"
822883 if local_part == "xml"
0 commit comments