// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package res import ( "fmt" "strings" rdpb "src/tools/ak/res/proto/res_data_go_proto" rmpb "src/tools/ak/res/proto/res_meta_go_proto" "google.golang.org/protobuf/proto" ) // FullyQualifiedName represents the components of a name. type FullyQualifiedName struct { Package string Type Type Name string } // ValuesResource represents a resource element. type ValuesResource struct { Src *PathInfo N FullyQualifiedName Payload []byte } // SetResource sets all the name related fields on the top level resource proto. func (f FullyQualifiedName) SetResource(r *rdpb.Resource) error { rt, err := f.Type.Enum() if err != nil { return err } r.ResourceType = rt r.Name = protoNameSanitizer.Replace(f.Name) return nil } // SetMetaData sets all name related fields for this style on a StyleableMetaData proto func (f FullyQualifiedName) SetMetaData(md *rmpb.StyleableMetaData) error { if f.Type != Styleable { return ErrWrongType } md.Name = proto.String(protoNameSanitizer.Replace(f.Name)) return nil } var ( protoNameSanitizer = strings.NewReplacer(".", "_") javaNameSanitizer = strings.NewReplacer(":", "_", ".", "_") ) // JavaName returns a version of the FullyQualifiedName that should be used for resource identifier fields. func (f FullyQualifiedName) JavaName() (string, error) { if !f.Type.IsReal() { return "", ErrWrongType } return javaNameSanitizer.Replace(f.Name), nil } // StyleableAttrName creates the java identifier for referencing this attribute in the given // style. func StyleableAttrName(styleable, attr FullyQualifiedName) (string, error) { if styleable.Type != Styleable || attr.Type != Attr { return "", ErrWrongType } js, err := styleable.JavaName() if err != nil { return "", err } ja, err := attr.JavaName() if err != nil { return "", err } if attr.Package == "android" { return fmt.Sprintf("%s_android_%s", js, ja), nil } return fmt.Sprintf("%s_%s", js, ja), nil } // ParseName is given a name string and optional context about the name (what type the name may be) // and attempts to extract the local name, Type, and package from the unparsed input. The format of // unparsed names is flexible and not well specified. // A FullyQualifiedName's String method will emit pkg:type/name which every tool understands, but // ParseName will encounter input like ?type:pkg/name - an undocumented, but legal way to specify a // reference to a style. If unparsed is so mangled that a legal name cannot possibly be determined, // it will return an error. func ParseName(unparsed string, resType Type) (FullyQualifiedName, error) { fqn := removeRef(unparsed) fqn.Type = resType pkgIdx := strings.Index(fqn.Name, ":") typeIdx := strings.Index(fqn.Name, "/") if pkgIdx == 0 || typeIdx == 0 { return FullyQualifiedName{}, fmt.Errorf("malformed name %q - can not start with ':' or '/'", unparsed) } if typeIdx != -1 { if pkgIdx != -1 { if pkgIdx < typeIdx { // Package, type and name (pkg:type/name) t, err := ParseType(fqn.Name[pkgIdx+1 : typeIdx]) if err != nil { // the name has illegal type in it that we'll never be able to scrub out. return FullyQualifiedName{}, err } fqn.Type = t fqn.Package = fqn.Name[:pkgIdx] fqn.Name = fqn.Name[typeIdx+1:] } else { // Package, type and name, type and package swapped (type:pkg/name) t, err := ParseType(fqn.Name[:typeIdx]) if err != nil { // the name has illegal type in it that we'll never be able to scrub out. return FullyQualifiedName{}, err } fqn.Type = t fqn.Package = fqn.Name[typeIdx+1 : pkgIdx] fqn.Name = fqn.Name[pkgIdx+1:] } } else { // Only type and name (type/name) t, err := ParseType(fqn.Name[:typeIdx]) if err != nil { // the name has illegal type in it that we'll never be able to scrub out. return FullyQualifiedName{}, err } fqn.Type = t fqn.Name = fqn.Name[typeIdx+1:] } } else { // Only package and name (pkg:name) if pkgIdx != -1 { fqn.Package = fqn.Name[:pkgIdx] fqn.Name = fqn.Name[pkgIdx+1:] } } if fqn.Package == "" { fqn.Package = "res-auto" } if fqn.Type == UnknownType { return FullyQualifiedName{}, fmt.Errorf("cannot determine type from %q and %v - not a valid name", unparsed, resType) } if fqn.Name == "" { return FullyQualifiedName{}, fmt.Errorf("cannot determine name from %q and %v - not a valid name", unparsed, resType) } return fqn, nil } func removeRef(unparsed string) (fqn FullyQualifiedName) { fqn.Name = unparsed if len(fqn.Name) > 2 && (strings.HasPrefix(fqn.Name, "@") || strings.HasPrefix(fqn.Name, "?")) { fqn.Name = fqn.Name[1:] } return } func (f FullyQualifiedName) String() string { return fmt.Sprintf("%s:%s/%s", f.Package, f.Type, f.Name) }