1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package liteparse 16 17import ( 18 "context" 19 "encoding/xml" 20 "fmt" 21 22 rdpb "src/tools/ak/res/proto/res_data_go_proto" 23 rmpb "src/tools/ak/res/proto/res_meta_go_proto" 24 "src/tools/ak/res/res" 25 "src/tools/ak/res/respipe/respipe" 26 "src/tools/ak/res/resxml/resxml" 27) 28 29// valuesParse handles all tags beneath <resources> and extracts the associated 30// ResourceType/names. Any encountered resources or errors are passed back on the returned channels. 31func valuesParse(ctx context.Context, xmlC <-chan resxml.XMLEvent) (<-chan *rdpb.Resource, <-chan error) { 32 resC := make(chan *rdpb.Resource) 33 errC := make(chan error) 34 go func() { 35 defer close(resC) 36 defer close(errC) 37 for { 38 xe, ok := resxml.ConsumeUntil(res.ResourcesTagName, xmlC) 39 if !ok { 40 return 41 } 42 resChildrenC := resxml.ForwardChildren(ctx, xe, xmlC) 43 for xe := range resChildrenC { 44 se, ok := xe.Token.(xml.StartElement) 45 if !ok { 46 // we ignore all non-start elements during a mini-parse. 47 continue 48 } 49 50 tagChildrenC := resxml.ForwardChildren(ctx, xe, resChildrenC) 51 ctx := respipe.PrefixErr(ctx, fmt.Sprintf("tag-name: %s at: %d: ", se.Name, xe.Offset)) 52 if t, ok := res.ResourcesTagToType[se.Name.Local]; ok { 53 if !minResChildParse(ctx, xe, t, tagChildrenC, resC, errC) { 54 return 55 } 56 } else if resxml.SloppyMatches(se.Name, res.ItemTagName) { 57 if !itemParse(ctx, xe, tagChildrenC, resC, errC) { 58 return 59 } 60 } 61 for range tagChildrenC { 62 // exhaust any children beneath this tag, we did not need them in the mini-parse. 63 } 64 } 65 } 66 }() 67 return resC, errC 68} 69 70// itemParse handles <item name="xxxx" type="yyy"></item> tags that are children of <resources/> 71func itemParse(ctx context.Context, xe resxml.XMLEvent, childC <-chan resxml.XMLEvent, resC chan<- *rdpb.Resource, errC chan<- error) bool { 72 name, err := extractName(xe) 73 if err != nil { 74 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: expected to encounter name attribute: %v", xe, err)) 75 } 76 var tv string 77 for _, a := range resxml.Attrs(xe) { 78 if resxml.SloppyMatches(res.TypeAttrName, a.Name) { 79 tv = a.Value 80 } 81 } 82 if tv == "" { 83 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: needs type atttribute", xe)) 84 } 85 t, err := res.ParseType(tv) 86 if err != nil { 87 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%q: cannot convert to type: %v", tv, err)) 88 } 89 fqn, err := res.ParseName(name, t) 90 if err != nil { 91 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%q / type: %s: convert to fqn: %v", name, t, err)) 92 } 93 r := new(rdpb.Resource) 94 if err := fqn.SetResource(r); err != nil { 95 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: name->proto failed: %v", fqn, err)) 96 } 97 return respipe.SendRes(ctx, resC, r) 98} 99 100// Returns the value of the name attribute or an error. 101func extractName(xe resxml.XMLEvent) (string, error) { 102 for _, a := range resxml.Attrs(xe) { 103 if resxml.SloppyMatches(res.NameAttrName, a.Name) { 104 return a.Value, nil 105 } 106 } 107 return "", fmt.Errorf("Expected to encounter name attribute within: %v", resxml.Attrs(xe)) 108} 109 110// minResChildParse handles a single top-level tag beneath <resources> and extracts all ResourceTypes/Names beneath it. It returns false if it detects that the context is done. 111func minResChildParse(ctx context.Context, xe resxml.XMLEvent, t res.Type, childC <-chan resxml.XMLEvent, resC chan<- *rdpb.Resource, errC chan<- error) bool { 112 name, err := extractName(xe) 113 if err != nil { 114 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%#v: needs name attribute: %v", xe, err)) 115 } 116 117 fqn, err := res.ParseName(name, t) 118 if err != nil { 119 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%s: invalid name: %v", name, err)) 120 } 121 122 r := new(rdpb.Resource) 123 if err := fqn.SetResource(r); err != nil { 124 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: name->proto failed: %v", fqn, err)) 125 } 126 if fqn.Type == res.Styleable { 127 md, ok := parseStyleableChildren(ctx, childC, resC, errC) 128 if !ok { 129 return false 130 } 131 if err := fqn.SetMetaData(md); err != nil { 132 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: could not set stylablemeta: %v", fqn, err)) 133 } 134 r.StyleableValue = md 135 } 136 if fqn.Type == res.Attr && !parseAttrChildren(ctx, childC, resC, errC) { 137 return false 138 } 139 140 return respipe.SendRes(ctx, resC, r) 141} 142 143// parseAttrChildren looks at the children of an <attr> tag and determines if any of them creates resources. 144// If it realizes that the provided ctx is canceled, it returns true, otherwise false. 145func parseAttrChildren(ctx context.Context, xmlC <-chan resxml.XMLEvent, resC chan<- *rdpb.Resource, errC chan<- error) bool { 146 for c := range xmlC { 147 ce, ok := c.Token.(xml.StartElement) 148 if !ok { 149 // do not care about non-start element events. 150 continue 151 } 152 if !resxml.SloppyMatches(res.EnumTagName, ce.Name) && !resxml.SloppyMatches(res.FlagTagName, ce.Name) { 153 // only want <enum> or <flag> elements 154 continue 155 } 156 157 enumFlagName, err := extractName(c) 158 if err != nil { 159 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: flag / enum should have had a name attribute: %v", ce, err)) 160 } 161 cFqn, err := res.ParseName(enumFlagName, res.ID) 162 if err != nil { 163 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: could not parse child of <attr>: %v", ce, err)) 164 } 165 cr := new(rdpb.Resource) 166 if err := cFqn.SetResource(cr); err != nil { 167 return respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: name->proto failed: %v", ce, err)) 168 } 169 if !respipe.SendRes(ctx, resC, cr) { 170 return false 171 } 172 } 173 return true 174} 175 176// parseStyleableChildren looks at the children of a <declare-stylable> tag and determines what resources they create. 177func parseStyleableChildren(ctx context.Context, xmlC <-chan resxml.XMLEvent, resC chan<- *rdpb.Resource, errC chan<- error) (*rmpb.StyleableMetaData, bool) { 178 var attrNames []string 179 for c := range xmlC { 180 if _, ok := c.Token.(xml.StartElement); !ok { 181 // skip events besides start element. 182 continue 183 } 184 name, err := extractName(c) 185 if err != nil { 186 // being liberal with what we can encounter under a <declare-styleable> tag. 187 continue 188 } 189 attrFqn, err := res.ParseName(name, res.Attr) 190 if err != nil { 191 return nil, respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%q: could not parse name to fqn: %v", name, err)) 192 } 193 if attrFqn.Type != res.Attr { 194 return nil, respipe.SendErr( 195 ctx, errC, respipe.Errorf(ctx, "%v: name->nameid proto failed: %v", attrFqn, res.ErrWrongType)) 196 } 197 198 attrNames = append(attrNames, attrFqn.String()) 199 if attrFqn.Package == "android" { 200 // since we're not generating android attributes (they already exist already) 201 // omit the resource proto for these attrs. 202 continue 203 } 204 205 if attrFqn.Type == res.Attr { 206 ctx := respipe.PrefixErr(ctx, fmt.Sprintf("%q: <attr> child: ", name)) 207 childC := resxml.ForwardChildren(ctx, c, xmlC) 208 if !parseAttrChildren(ctx, childC, resC, errC) { 209 return nil, false 210 } 211 } 212 213 attrR := new(rdpb.Resource) 214 if err := attrFqn.SetResource(attrR); err != nil { 215 return nil, respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "%v: name->proto failed: %v", attrFqn, err)) 216 } 217 218 if !respipe.SendRes(ctx, resC, attrR) { 219 return nil, false 220 } 221 222 } 223 return &rmpb.StyleableMetaData{ 224 FqnAttributes: attrNames, 225 }, true 226} 227