xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/res/resxml/xml_parser.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1// Copyright 2022 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
15// Package resxml contains common functions to extract information from xml files and feed that information into the resource processing pipeline.
16package resxml
17
18import (
19	"context"
20	"encoding/xml"
21	"io"
22
23	"src/tools/ak/res/respipe/respipe"
24)
25
26// XMLEvent wraps an XMLToken and the Offset at which it was encountered.
27type XMLEvent struct {
28	Token  xml.Token
29	Offset int64
30}
31
32// ConsumeUntil takes xmlEvents from the provided chan and discards them until it finds a StartEvent which matches the provided name. If the channel is exhausted, false is returned.
33func ConsumeUntil(name xml.Name, xmlC <-chan XMLEvent) (XMLEvent, bool) {
34	for xe := range xmlC {
35		if se, ok := xe.Token.(xml.StartElement); ok {
36			if SloppyMatches(name, se.Name) {
37				return xe, true
38			}
39		}
40	}
41	return XMLEvent{}, false
42}
43
44// ForwardChildren takes the provided StartElement and a channel of XMLEvents and forwards that all events onto the returned XMLEvent channel until the matching EndElement to start is encountered.
45func ForwardChildren(ctx context.Context, start XMLEvent, xmlC <-chan XMLEvent) <-chan XMLEvent {
46	eventC := make(chan XMLEvent, 1)
47	se := start.Token.(xml.StartElement)
48	go func() {
49		defer close(eventC)
50		count := 1
51		for xe := range xmlC {
52			if e, ok := xe.Token.(xml.StartElement); ok {
53				if StrictMatches(e.Name, se.Name) {
54					count++
55				}
56			}
57			if e, ok := xe.Token.(xml.EndElement); ok {
58				if StrictMatches(e.Name, se.Name) {
59					count--
60				}
61				if count == 0 {
62					return
63				}
64			}
65			if !SendXML(ctx, eventC, xe) {
66				return
67			}
68		}
69	}()
70	return eventC
71
72}
73
74// StrictMatches considers xml.Names equal if both their space and name matches.
75func StrictMatches(n1, n2 xml.Name) bool {
76	return n1.Local == n2.Local && n1.Space == n2.Space
77}
78
79// SloppyMatches ignores xml.Name Space attributes unless both names specify Space. Otherwise
80// only the Local attribute is used for matching.
81func SloppyMatches(n1, n2 xml.Name) bool {
82	if n1.Space != "" && n2.Space != "" {
83		return StrictMatches(n1, n2)
84	}
85	return n1.Local == n2.Local
86}
87
88// StreamDoc parses the provided doc and forwards all xml tokens to the returned XMLEvent chan.
89func StreamDoc(ctx context.Context, doc io.Reader) (<-chan XMLEvent, <-chan error) {
90	eventC := make(chan XMLEvent)
91	errC := make(chan error)
92	go func() {
93		defer close(eventC)
94		defer close(errC)
95		decoder := xml.NewDecoder(doc)
96		// Turns off unknown entities check. Would otherwise fail on resources
97		// using non-standard XML entities.
98		decoder.Strict = false
99		for {
100			tok, err := decoder.Token()
101			if err == io.EOF {
102				return
103			}
104			if err != nil {
105				respipe.SendErr(ctx, errC, respipe.Errorf(ctx, "offset: %d xml error: %v", decoder.InputOffset(), err))
106				return
107			}
108			tok = xml.CopyToken(tok)
109			if !SendXML(ctx, eventC, XMLEvent{tok, decoder.InputOffset()}) {
110				return
111			}
112		}
113	}()
114	return eventC, errC
115}
116
117// SendXML sends an XMLEvent to the provided channel and returns true, otherwise if the context is done, it returns false.
118func SendXML(ctx context.Context, xmlC chan<- XMLEvent, xml XMLEvent) bool {
119	select {
120	case <-ctx.Done():
121		return false
122	case xmlC <- xml:
123		return true
124	}
125}
126
127// Attrs returns all []xml.Attrs encounted on an XMLEvent.
128func Attrs(xe XMLEvent) []xml.Attr {
129	if se, ok := xe.Token.(xml.StartElement); ok {
130		return se.Attr
131	}
132	return nil
133}
134