xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/liteparse/values_parse.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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