source: vlo/trunk/vlo-importer/src/main/java/eu/clarin/cmdi/vlo/importer/CMDIParserVTDXML.java @ 5979

Last change on this file since 5979 was 5979, checked in by teckart@informatik.uni-leipzig.de, 9 years ago

Added support for using local XML schema files instead of using the component registry (#522), also stricter check when extracting profile ID from CMDI instance file

File size: 10.6 KB
Line 
1package eu.clarin.cmdi.vlo.importer;
2
3import com.ximpleware.AutoPilot;
4import com.ximpleware.NavException;
5import com.ximpleware.VTDException;
6import com.ximpleware.VTDGen;
7import com.ximpleware.VTDNav;
8import com.ximpleware.XPathEvalException;
9import com.ximpleware.XPathParseException;
10import eu.clarin.cmdi.vlo.FacetConstants;
11import java.io.File;
12import java.io.FileInputStream;
13import java.io.IOException;
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Map;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19import org.apache.commons.io.IOUtils;
20import org.slf4j.Logger;
21import org.slf4j.LoggerFactory;
22
23public class CMDIParserVTDXML implements CMDIDataProcessor {
24    private final Map<String, PostProcessor> postProcessors;
25    private final Boolean useLocalXSDCache;
26    private static final Pattern PROFILE_ID_PATTERN = Pattern.compile(".*(clarin.eu:cr1:p_[0-9]+).*");
27    private final static Logger LOG = LoggerFactory.getLogger(CMDIParserVTDXML.class);
28   
29    private static final String DEFAULT_LANGUAGE = "und";
30
31    public CMDIParserVTDXML(Map<String, PostProcessor> postProcessors, Boolean useLocalXSDCache) {
32        this.postProcessors = postProcessors;
33        this.useLocalXSDCache = useLocalXSDCache;
34    }
35
36    @Override
37    public CMDIData process(File file) throws VTDException, IOException {
38        CMDIData cmdiData = new CMDIData();
39        VTDGen vg = new VTDGen();
40        FileInputStream fileInputStream = new FileInputStream(file); 
41        vg.setDoc(IOUtils.toByteArray(fileInputStream));
42        vg.parse(true);
43        fileInputStream.close();
44       
45        VTDNav nav = vg.getNav();
46        FacetMapping facetMapping = getFacetMapping(nav.cloneNav());
47
48        if(facetMapping.getFacets().isEmpty()){
49            LOG.error("Problems mapping facets for file: {}", file.getAbsolutePath());
50        }
51
52        nav.toElement(VTDNav.ROOT);
53        processResources(cmdiData, nav);
54        processFacets(cmdiData, nav, facetMapping);
55        return cmdiData;
56    }
57
58    /**
59     * Setting namespace for Autopilot ap
60     * @param ap
61     */
62    private void setNameSpace(AutoPilot ap) {
63        ap.declareXPathNameSpace("c", "http://www.clarin.eu/cmd/");
64    }
65
66    /**
67     * Extracts valid XML patterns for all facet definitions
68     * @param nav VTD Navigator
69     * @return the facet mapping used to map meta data to facets
70     * @throws VTDException
71     */
72    private FacetMapping getFacetMapping(VTDNav nav) throws VTDException {
73        String profileId = extractXsd(nav);
74        if (profileId == null) {
75            throw new RuntimeException("Cannot get xsd schema so cannot get a proper mapping. Parse failed!");
76        }
77        String facetConceptsFile = MetadataImporter.config.getFacetConceptsFile();
78        if (facetConceptsFile.length() == 0){
79            // use the packaged facet mapping file
80            facetConceptsFile = "/facetConcepts.xml";
81        }
82        return FacetMappingFactory.getFacetMapping(facetConceptsFile, profileId, useLocalXSDCache);
83    }
84
85    /**
86     * Try two approaches to extract the XSD schema information from the CMDI file
87     * @param nav VTD Navigator
88     * @return ID of CMDI schema, or null if neither the CMDI header nor the XMLSchema-instance's attributes contained the information
89     * @throws VTDException
90     */
91    String extractXsd(VTDNav nav) throws VTDException {
92        String profileID = getProfileIdFromHeader(nav);
93        if (profileID == null) {
94            profileID = getProfileIdFromSchemaLocation(nav);
95        }
96        return profileID;
97    }
98
99    /**
100     * Extract XSD schema information from CMDI header (using element //Header/MdProfile)
101     * @param nav VTD Navigator
102     * @return ID of CMDI schema, or null if content of //Header/MdProfile element could not be read
103     * @throws XPathParseException
104     * @throws XPathEvalException
105     * @throws NavException
106     */
107    private String getProfileIdFromHeader(VTDNav nav) throws XPathParseException, XPathEvalException, NavException {
108        nav.toElement(VTDNav.ROOT);
109        AutoPilot ap = new AutoPilot(nav);
110        setNameSpace(ap);
111        ap.selectXPath("/c:CMD/c:Header/c:MdProfile/text()");
112        int index = ap.evalXPath();
113        String profileId = null;
114        if (index != -1) {
115            profileId = nav.toString(index).trim();
116        }
117        return profileId;
118    }
119
120    /**
121     * Extract XSD schema information from schemaLocation or noNamespaceSchemaLocation attributes
122     * @param nav VTD Navigator
123     * @return ID of CMDI schema, or null if attributes don't exist
124     * @throws NavException
125     */
126    private String getProfileIdFromSchemaLocation(VTDNav nav) throws NavException {
127        String result = null;
128        nav.toElement(VTDNav.ROOT);
129        int index = nav.getAttrValNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
130        if (index != -1) {
131            String schemaLocation = nav.toNormalizedString(index);
132            result = schemaLocation.split(" ")[1];
133        } else {
134            index = nav.getAttrValNS("http://www.w3.org/2001/XMLSchema-instance", "noNamespaceSchemaLocation");
135            if (index != -1) {
136                result = nav.toNormalizedString(index);
137            }
138        }
139       
140        // extract profile ID
141        if(result != null) {
142        Matcher m = PROFILE_ID_PATTERN.matcher(result);
143        if(m.find())
144            return m.group(1);
145        }
146        return null;
147    }
148   
149    /**
150     * Extract ResourceProxies from ResourceProxyList
151     * @param cmdiData representation of the CMDI document
152     * @param nav VTD Navigator
153     * @throws VTDException
154     */
155    private void processResources(CMDIData cmdiData, VTDNav nav) throws VTDException {
156       
157        AutoPilot resourceProxy = new AutoPilot(nav);
158        setNameSpace(resourceProxy);
159        resourceProxy.selectXPath("/c:CMD/c:Resources/c:ResourceProxyList/c:ResourceProxy");
160       
161        AutoPilot resourceRef = new AutoPilot(nav);
162        setNameSpace(resourceRef);
163        resourceRef.selectXPath("c:ResourceRef");
164       
165        AutoPilot resourceType = new AutoPilot(nav);
166        setNameSpace(resourceType);
167        resourceType.selectXPath("c:ResourceType");
168       
169        AutoPilot resourceMimeType = new AutoPilot(nav);
170        setNameSpace(resourceMimeType);
171        resourceMimeType.selectXPath("c:ResourceType/@mimetype");
172       
173        while (resourceProxy.evalXPath() != -1) {
174            String ref = resourceRef.evalXPathToString();
175            String type = resourceType.evalXPathToString();
176            String mimeType = resourceMimeType.evalXPathToString();
177           
178            if (!ref.equals("") && !type.equals("")) {
179                // note that the mime type could be empty
180                cmdiData.addResource(ref, type, mimeType);
181            }
182        }
183    }
184
185    /**
186     * Extracts facet values according to the facetMapping
187     * @param cmdiData representation of the CMDI document
188     * @param nav VTD Navigator
189     * @param facetMapping the facet mapping used to map meta data to facets
190     * @throws VTDException
191     */
192    private void processFacets(CMDIData cmdiData, VTDNav nav, FacetMapping facetMapping) throws VTDException {
193        List<FacetConfiguration> facetList = facetMapping.getFacets();
194        for (FacetConfiguration config : facetList) {
195            List<String> patterns = config.getPatterns();
196            for (String pattern : patterns) {
197                boolean matchedPattern = matchPattern(cmdiData, nav, config, pattern, config.getAllowMultipleValues());
198                if (matchedPattern && !config.getAllowMultipleValues()) {
199                    break;
200                }
201            }
202        }
203    }
204
205    /**
206     * Extracts content from CMDI file for a specific facet based on a single XPath expression
207     * @param cmdiData representation of the CMDI document
208     * @param nav VTD Navigator
209     * @param config facet configuration
210     * @param pattern XPath expression
211     * @param allowMultipleValues information if multiple values are allowed in this facet
212     * @return pattern matched a node in the CMDI file?
213     * @throws VTDException
214     */
215    private boolean matchPattern(CMDIData cmdiData, VTDNav nav, FacetConfiguration config, String pattern, Boolean allowMultipleValues) throws VTDException {
216        boolean matchedPattern = false;
217        AutoPilot ap = new AutoPilot(nav);
218        setNameSpace(ap);
219        ap.selectXPath(pattern);
220        int index = ap.evalXPath();
221        while (index != -1) {
222            matchedPattern = true;
223            if (nav.getTokenType(index) == VTDNav.TOKEN_ATTR_NAME) {
224                //if it is an attribute you need to add 1 to the index to get the right value
225                index++;
226            }
227            String value = nav.toString(index);
228           
229            // extract language code in xml:lang if available
230            Integer langAttrIndex = nav.getAttrVal("xml:lang");
231            String languageCode = DEFAULT_LANGUAGE;
232            if(langAttrIndex != -1)
233                languageCode = nav.toString(langAttrIndex).trim();
234            // replace 2-letter with 3-letter codes
235            if(LanguageCodeUtils.getSilToIso639Map().containsKey(languageCode))
236                languageCode = LanguageCodeUtils.getSilToIso639Map().get(languageCode);
237           
238            List<String> valueList = postProcess(config.getName(), value);
239            for(int i=0; i<valueList.size(); i++) {
240                if(!allowMultipleValues && i>0)
241                    break;
242                String fieldValue = valueList.get(i).trim();
243                if(config.getName().equals(FacetConstants.FIELD_DESCRIPTION))
244                    fieldValue = "{lang='"+languageCode+"'}"+fieldValue;
245                cmdiData.addDocField(config.getName(), fieldValue, config.isCaseInsensitive());
246            }
247            index = ap.evalXPath();
248           
249            if(!allowMultipleValues)
250                break;
251        }
252        return matchedPattern;
253    }
254
255    /**
256     * Applies registered PostProcessor to extracted values
257     * @param facetName name of the facet for which value was extracted
258     * @param extractedValue extracted value from CMDI file
259     * @return value after applying matching PostProcessor or the original value if no PostProcessor was registered for the facet
260     */
261    private List<String> postProcess(String facetName, String extractedValue) {
262        List<String> resultList = new ArrayList<String>();
263        if (postProcessors.containsKey(facetName)) {
264            PostProcessor processor = postProcessors.get(facetName);
265            resultList = processor.process(extractedValue);
266        } else {
267            resultList.add(extractedValue);
268        }
269        return resultList;
270    }
271}
Note: See TracBrowser for help on using the repository browser.