1111//! attribute or a mismatched close tag makes the whole file `None`.
1212
1313use std:: collections:: BTreeSet ;
14+ use std:: ops:: Range ;
1415
1516// ── pure reader ──
1617
@@ -28,6 +29,47 @@ pub(crate) struct NugetConfig {
2829 pub ( crate ) mappings : Vec < ( String , Vec < String > ) > ,
2930 /// Keys `configuration/disabledPackageSources` turns off.
3031 pub ( crate ) disabled : BTreeSet < String > ,
32+ /// Live XML locations for writers. Routing readers and writers share
33+ /// the same treatment of comments, quoted attributes and element scope.
34+ pub ( crate ) configuration : Option < ConfigSection > ,
35+ pub ( crate ) package_sources : Option < ConfigSection > ,
36+ pub ( crate ) source_mapping : Option < ConfigSection > ,
37+ /// Preserve the routing reader's behavior on repeated sections, but do
38+ /// not let a writer guess which occurrence should receive an edit.
39+ pub ( crate ) repeated_sections : bool ,
40+ }
41+
42+ #[ derive( Debug ) ]
43+ pub ( crate ) struct ConfigSection {
44+ pub ( crate ) open : Range < usize > ,
45+ /// `None` for a self-closing element (unclosed XML fails parsing).
46+ pub ( crate ) close_start : Option < usize > ,
47+ /// After the last direct `<clear>` child, or the opening tag otherwise.
48+ pub ( crate ) insert_at : usize ,
49+ }
50+
51+ fn section_mut < ' a > (
52+ cfg : & ' a mut NugetConfig ,
53+ parents : & [ & str ] ,
54+ name : & str ,
55+ ) -> Option < & ' a mut Option < ConfigSection > > {
56+ match ( parents, name) {
57+ ( [ ] , "configuration" ) => Some ( & mut cfg. configuration ) ,
58+ ( [ "configuration" ] , "packageSources" ) => Some ( & mut cfg. package_sources ) ,
59+ ( [ "configuration" ] , "packageSourceMapping" ) => Some ( & mut cfg. source_mapping ) ,
60+ _ => None ,
61+ }
62+ }
63+
64+ fn record_clear ( cfg : & mut NugetConfig , parents : & [ & str ] , end : usize ) {
65+ let section = match parents {
66+ [ "configuration" , "packageSources" ] => cfg. package_sources . as_mut ( ) ,
67+ [ "configuration" , "packageSourceMapping" ] => cfg. source_mapping . as_mut ( ) ,
68+ _ => None ,
69+ } ;
70+ if let Some ( section) = section {
71+ section. insert_at = end;
72+ }
3173}
3274
3375/// One open (or self-closing) tag.
@@ -68,13 +110,33 @@ pub(crate) fn parse_config(text: &str) -> Option<NugetConfig> {
68110 i = at + rest. find ( '>' ) ? + 1 ;
69111 } else if let Some ( close) = rest. strip_prefix ( "</" ) {
70112 let end = close. find ( '>' ) ?;
71- if stack. pop ( ) ? != close[ ..end] . trim ( ) {
113+ let name = stack. pop ( ) ?;
114+ if name != close[ ..end] . trim ( ) {
72115 return None ;
73116 }
74117 i = at + 2 + end + 1 ;
118+ if let Some ( Some ( section) ) = section_mut ( & mut cfg, & stack, name) {
119+ section. close_start = Some ( at) ;
120+ }
121+ if name == "clear" {
122+ record_clear ( & mut cfg, & stack, i) ;
123+ }
75124 } else {
76125 let ( tag, consumed) = parse_open_tag ( & rest[ 1 ..] ) ?;
77126 i = at + 1 + consumed;
127+ if let Some ( slot) = section_mut ( & mut cfg, & stack, tag. name ) {
128+ let repeated = slot
129+ . replace ( ConfigSection {
130+ open : at..i,
131+ close_start : None ,
132+ insert_at : i,
133+ } )
134+ . is_some ( ) ;
135+ cfg. repeated_sections |= repeated;
136+ }
137+ if tag. name == "clear" && tag. self_closing {
138+ record_clear ( & mut cfg, & stack, i) ;
139+ }
78140 visit ( & stack, & tag, & mut cfg, & mut open_mapping) ;
79141 if !tag. self_closing {
80142 if stack. len ( ) >= MAX_XML_DEPTH {
0 commit comments