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