1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.common.resources.deprecated;
18 
19 import com.android.ide.common.rendering.api.ArrayResourceValueImpl;
20 import com.android.ide.common.rendering.api.AttrResourceValueImpl;
21 import com.android.ide.common.rendering.api.ResourceNamespace;
22 import com.android.ide.common.rendering.api.ResourceReference;
23 import com.android.ide.common.rendering.api.ResourceValue;
24 import com.android.ide.common.rendering.api.ResourceValueImpl;
25 import com.android.ide.common.rendering.api.StyleItemResourceValue;
26 import com.android.ide.common.rendering.api.StyleItemResourceValueImpl;
27 import com.android.ide.common.rendering.api.StyleResourceValueImpl;
28 import com.android.ide.common.rendering.api.StyleableResourceValueImpl;
29 import com.android.ide.common.resources.ValueXmlHelper;
30 import com.android.resources.ResourceType;
31 
32 import org.xml.sax.Attributes;
33 import org.xml.sax.SAXException;
34 import org.xml.sax.helpers.DefaultHandler;
35 
36 import com.google.common.base.Strings;
37 
38 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
39 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX_LEN;
40 import static com.android.SdkConstants.ATTR_NAME;
41 import static com.android.SdkConstants.ATTR_PARENT;
42 import static com.android.SdkConstants.ATTR_VALUE;
43 import static com.android.SdkConstants.TAG_RESOURCES;
44 
45 /**
46  * @deprecated This class is part of an obsolete resource repository system that is no longer used
47  *     in production code. The class is preserved temporarily for LayoutLib tests.
48  */
49 @Deprecated
50 public final class ValueResourceParser extends DefaultHandler {
51 
52     private static final ResourceReference TMP_REF =
53             new ResourceReference(ResourceNamespace.RES_AUTO, ResourceType.STRING, "_tmp");
54 
55     public interface IValueResourceRepository {
addResourceValue(ResourceValue value)56         void addResourceValue(ResourceValue value);
57     }
58 
59     private boolean inResources;
60     private int mDepth;
61     private ResourceValueImpl mCurrentValue;
62     private ArrayResourceValueImpl mArrayResourceValue;
63     private StyleResourceValueImpl mCurrentStyle;
64     private StyleableResourceValueImpl mCurrentDeclareStyleable;
65     private AttrResourceValueImpl mCurrentAttr;
66     private final IValueResourceRepository mRepository;
67     private final boolean mIsFramework;
68     private final String mLibraryName;
69 
ValueResourceParser(IValueResourceRepository repository, boolean isFramework, String libraryName)70     public ValueResourceParser(IValueResourceRepository repository, boolean isFramework, String libraryName) {
71         mRepository = repository;
72         mIsFramework = isFramework;
73         mLibraryName = libraryName;
74     }
75 
76     @Override
endElement(String uri, String localName, String qName)77     public void endElement(String uri, String localName, String qName) throws SAXException {
78         if (mCurrentValue != null) {
79             String value = mCurrentValue.getValue();
80             value = value == null ? "" : ValueXmlHelper.unescapeResourceString(value, false, true);
81             mCurrentValue.setValue(value);
82         }
83 
84         if (inResources && qName.equals(TAG_RESOURCES)) {
85             inResources = false;
86         } else if (mDepth == 2) {
87             mCurrentValue = null;
88             mCurrentStyle = null;
89             mCurrentDeclareStyleable = null;
90             mCurrentAttr = null;
91             mArrayResourceValue = null;
92         } else if (mDepth == 3) {
93             if (mArrayResourceValue != null && mCurrentValue != null) {
94                 mArrayResourceValue.addElement(mCurrentValue.getValue());
95             }
96             mCurrentValue = null;
97             //noinspection VariableNotUsedInsideIf
98             if (mCurrentDeclareStyleable != null) {
99                 mCurrentAttr = null;
100             }
101         }
102 
103         mDepth--;
104         super.endElement(uri, localName, qName);
105     }
106 
107     @Override
startElement(String uri, String localName, String qName, Attributes attributes)108     public void startElement(String uri, String localName, String qName, Attributes attributes)
109             throws SAXException {
110         try {
111             ResourceNamespace namespace = ResourceNamespace.fromBoolean(mIsFramework);
112             mDepth++;
113             if (!inResources && mDepth == 1) {
114                 if (qName.equals(TAG_RESOURCES)) {
115                     inResources = true;
116                 }
117             } else if (mDepth == 2 && inResources) {
118                 ResourceType type =
119                         ResourceType.fromXmlTag(
120                                 new Object(), (t) -> qName, (t, name) -> attributes.getValue(name));
121 
122                 if (type != null) {
123                     // get the resource name
124                     String name = attributes.getValue(ATTR_NAME);
125                     if (name != null) {
126                         switch (type) {
127                             case STYLE:
128                                 String parent = attributes.getValue(ATTR_PARENT);
129                                 mCurrentStyle =
130                                         new StyleResourceValueImpl(
131                                                 namespace, name, parent, mLibraryName);
132                                 mRepository.addResourceValue(mCurrentStyle);
133                                 break;
134                             case STYLEABLE:
135                                 mCurrentDeclareStyleable =
136                                         new StyleableResourceValueImpl(
137                                                 namespace, name, null, mLibraryName);
138                                 mRepository.addResourceValue(mCurrentDeclareStyleable);
139                                 break;
140                             case ATTR:
141                                 mCurrentAttr =
142                                         new AttrResourceValueImpl(namespace, name, mLibraryName);
143                                 mRepository.addResourceValue(mCurrentAttr);
144                                 break;
145                             case ARRAY:
146                                 mArrayResourceValue =
147                                         new ArrayResourceValueImpl(namespace, name, mLibraryName);
148                                 mRepository.addResourceValue(mArrayResourceValue);
149                                 break;
150                             default:
151                                 mCurrentValue =
152                                         new ResourceValueImpl(
153                                                 namespace, type, name, null, mLibraryName);
154                                 mRepository.addResourceValue(mCurrentValue);
155                                 break;
156                         }
157                     }
158                 }
159             } else if (mDepth == 3) {
160                 // get the resource name
161                 String name = attributes.getValue(ATTR_NAME);
162                 if (!Strings.isNullOrEmpty(name)) {
163                     if (mCurrentStyle != null) {
164                         mCurrentValue =
165                                 new StyleItemResourceValueImpl(
166                                         mCurrentStyle.getNamespace(), name, null, mLibraryName);
167                         mCurrentStyle.addItem((StyleItemResourceValue) mCurrentValue);
168                     } else if (mCurrentDeclareStyleable != null) {
169                         if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
170                             name = name.substring(ANDROID_NS_NAME_PREFIX_LEN);
171                         }
172 
173                         mCurrentAttr = new AttrResourceValueImpl(namespace, name, mLibraryName);
174                         mCurrentDeclareStyleable.addValue(mCurrentAttr);
175 
176                         // also add it to the repository.
177                         mRepository.addResourceValue(mCurrentAttr);
178 
179                     } else if (mCurrentAttr != null) {
180                         // get the enum/flag value
181                         String value = attributes.getValue(ATTR_VALUE);
182 
183                         try {
184                             // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
185                             // use Long.decode instead.
186                             mCurrentAttr.addValue(name, Long.decode(value).intValue(), null);
187                         } catch (NumberFormatException e) {
188                             // pass, we'll just ignore this value
189                         }
190                     }
191                 } else //noinspection VariableNotUsedInsideIf
192                     if (mArrayResourceValue != null) {
193                     // Create a temporary resource value to hold the item's value. The value is
194                     // not added to the repository, since it's just a holder. The value will be set
195                     // in the `characters` method and then added to mArrayResourceValue in `endElement`.
196                     mCurrentValue = new ResourceValueImpl(TMP_REF, null);
197                     }
198             } else if (mDepth == 4 && mCurrentAttr != null) {
199                 // get the enum/flag name
200                 String name = attributes.getValue(ATTR_NAME);
201                 String value = attributes.getValue(ATTR_VALUE);
202 
203                 try {
204                     // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
205                     // use Long.decode instead.
206                     mCurrentAttr.addValue(name, Long.decode(value).intValue(), null);
207                 } catch (NumberFormatException e) {
208                     // pass, we'll just ignore this value
209                 }
210             }
211         } finally {
212             super.startElement(uri, localName, qName, attributes);
213         }
214     }
215 
216     @Override
characters(char[] ch, int start, int length)217     public void characters(char[] ch, int start, int length) {
218         if (mCurrentValue != null) {
219             String value = mCurrentValue.getValue();
220             if (value == null) {
221                 mCurrentValue.setValue(new String(ch, start, length));
222             } else {
223                 mCurrentValue.setValue(value + new String(ch, start, length));
224             }
225         }
226     }
227 }
228