1- use crate :: base:: { Bytes , BytesCow , eq_case_insensitive} ;
1+ use crate :: base:: { Bytes , BytesCow , SourceLocation , eq_case_insensitive} ;
22use crate :: errors:: RewritingError ;
33use crate :: html:: escape_double_quotes_only;
44use crate :: parser:: AttributeBuffer ;
@@ -41,6 +41,8 @@ pub struct Attribute<'i> {
4141 value : BytesCow < ' i > ,
4242 raw : Option < Bytes < ' i > > ,
4343 encoding : & ' static Encoding ,
44+ name_range : Option < std:: ops:: Range < usize > > ,
45+ value_range : Option < std:: ops:: Range < usize > > ,
4446}
4547
4648impl < ' i > Attribute < ' i > {
@@ -51,12 +53,16 @@ impl<'i> Attribute<'i> {
5153 value : BytesCow < ' i > ,
5254 raw : Bytes < ' i > ,
5355 encoding : & ' static Encoding ,
56+ name_range : Option < std:: ops:: Range < usize > > ,
57+ value_range : Option < std:: ops:: Range < usize > > ,
5458 ) -> Self {
5559 Attribute {
5660 name,
5761 value,
5862 raw : Some ( raw) ,
5963 encoding,
64+ name_range,
65+ value_range,
6066 }
6167 }
6268
@@ -104,10 +110,37 @@ impl<'i> Attribute<'i> {
104110 self . value . as_string ( self . encoding )
105111 }
106112
113+ /// Returns the source location of the attribute name in the original document.
114+ ///
115+ /// Returns `None` for attributes added programmatically via
116+ /// [`Element::set_attribute`][crate::html_content::Element::set_attribute].
117+ #[ inline]
118+ #[ must_use]
119+ pub fn name_source_location ( & self ) -> Option < SourceLocation > {
120+ self . name_range
121+ . as_ref ( )
122+ . map ( |r| SourceLocation :: from_start_len ( r. start , r. end - r. start ) )
123+ }
124+
125+ /// Returns the source location of the attribute value in the original document.
126+ ///
127+ /// The range covers only the value itself, excluding any quotes or the `=` sign.
128+ ///
129+ /// Returns `None` for attributes added programmatically via
130+ /// [`Element::set_attribute`][crate::html_content::Element::set_attribute].
131+ #[ inline]
132+ #[ must_use]
133+ pub fn value_source_location ( & self ) -> Option < SourceLocation > {
134+ self . value_range
135+ . as_ref ( )
136+ . map ( |r| SourceLocation :: from_start_len ( r. start , r. end - r. start ) )
137+ }
138+
107139 #[ inline]
108140 fn set_value ( & mut self , value : & str ) {
109141 self . value = BytesCow :: owned_from_str ( value, self . encoding ) ;
110142 self . raw = None ;
143+ self . value_range = None ;
111144 }
112145}
113146
@@ -141,6 +174,7 @@ pub(crate) struct Attributes<'i> {
141174 attribute_buffer : & ' i AttributeBuffer ,
142175 items : OnceCell < Vec < Attribute < ' i > > > ,
143176 pub ( crate ) encoding : & ' static Encoding ,
177+ source_byte_offset : usize ,
144178}
145179
146180impl < ' i > Attributes < ' i > {
@@ -150,12 +184,14 @@ impl<'i> Attributes<'i> {
150184 input : & ' i Bytes < ' i > ,
151185 attribute_buffer : & ' i AttributeBuffer ,
152186 encoding : & ' static Encoding ,
187+ source_byte_offset : usize ,
153188 ) -> Self {
154189 Attributes {
155190 input,
156191 attribute_buffer,
157192 items : OnceCell :: default ( ) ,
158193 encoding,
194+ source_byte_offset,
159195 }
160196 }
161197
@@ -210,6 +246,8 @@ impl<'i> Attributes<'i> {
210246 value : BytesCow :: owned_from_str ( value, encoding) ,
211247 raw : None ,
212248 encoding,
249+ name_range : None ,
250+ value_range : None ,
213251 } ) ;
214252 }
215253 }
@@ -240,6 +278,7 @@ impl<'i> Attributes<'i> {
240278 debug_assert ! ( false ) ;
241279 Bytes :: default ( )
242280 } ;
281+ let base = self . source_byte_offset ;
243282 self . attribute_buffer . iter ( ) . map ( move |a| {
244283 Attribute :: new (
245284 self . input
@@ -254,6 +293,8 @@ impl<'i> Attributes<'i> {
254293 . opt_slice ( Some ( a. raw_range ) )
255294 . unwrap_or_else ( cant_fail) ,
256295 self . encoding ,
296+ Some ( base + a. name . start ..base + a. name . end ) ,
297+ Some ( base + a. value . start ..base + a. value . end ) ,
257298 )
258299 } )
259300 }
0 commit comments