001: /*
002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/xml/content/CmsXmlContent.java,v $
003: * Date : $Date: 2008-02-27 12:05:36 $
004: * Version: $Revision: 1.42 $
005: *
006: * This library is part of OpenCms -
007: * the Open Source Content Management System
008: *
009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
010: *
011: * This library is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU Lesser General Public
013: * License as published by the Free Software Foundation; either
014: * version 2.1 of the License, or (at your option) any later version.
015: *
016: * This library is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: * Lesser General Public License for more details.
020: *
021: * For further information about Alkacon Software GmbH, please see the
022: * company website: http://www.alkacon.com
023: *
024: * For further information about OpenCms, please see the
025: * project website: http://www.opencms.org
026: *
027: * You should have received a copy of the GNU Lesser General Public
028: * License along with this library; if not, write to the Free Software
029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
030: */
031:
032: package org.opencms.xml.content;
033:
034: import org.opencms.file.CmsFile;
035: import org.opencms.file.CmsObject;
036: import org.opencms.file.CmsResource;
037: import org.opencms.file.CmsResourceFilter;
038: import org.opencms.i18n.CmsEncoder;
039: import org.opencms.i18n.CmsLocaleManager;
040: import org.opencms.main.CmsException;
041: import org.opencms.main.CmsIllegalArgumentException;
042: import org.opencms.main.CmsLog;
043: import org.opencms.main.CmsRuntimeException;
044: import org.opencms.staticexport.CmsLinkProcessor;
045: import org.opencms.staticexport.CmsLinkTable;
046: import org.opencms.util.CmsMacroResolver;
047: import org.opencms.xml.A_CmsXmlDocument;
048: import org.opencms.xml.CmsXmlContentDefinition;
049: import org.opencms.xml.CmsXmlException;
050: import org.opencms.xml.CmsXmlUtils;
051: import org.opencms.xml.I_CmsXmlDocument;
052: import org.opencms.xml.types.CmsXmlNestedContentDefinition;
053: import org.opencms.xml.types.I_CmsXmlContentValue;
054: import org.opencms.xml.types.I_CmsXmlSchemaType;
055:
056: import java.io.IOException;
057: import java.util.ArrayList;
058: import java.util.Collections;
059: import java.util.HashMap;
060: import java.util.HashSet;
061: import java.util.Iterator;
062: import java.util.List;
063: import java.util.Locale;
064: import java.util.Set;
065:
066: import org.apache.commons.logging.Log;
067:
068: import org.dom4j.Document;
069: import org.dom4j.Element;
070: import org.dom4j.Node;
071: import org.xml.sax.EntityResolver;
072: import org.xml.sax.SAXException;
073:
074: /**
075: * Implementation of a XML content object,
076: * used to access and manage structured content.<p>
077: *
078: * Use the {@link org.opencms.xml.content.CmsXmlContentFactory} to generate an
079: * instance of this class.<p>
080: *
081: * @author Alexander Kandzior
082: *
083: * @version $Revision: 1.42 $
084: *
085: * @since 6.0.0
086: */
087: public class CmsXmlContent extends A_CmsXmlDocument implements
088: I_CmsXmlDocument {
089:
090: /** The name of the XML content auto correction runtime attribute, this must always be a Boolean. */
091: public static final String AUTO_CORRECTION_ATTRIBUTE = CmsXmlContent.class
092: .getName()
093: + ".autoCorrectionEnabled";
094:
095: /** The property to set to enable xerces schema validation. */
096: public static final String XERCES_SCHEMA_PROPERTY = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation";
097:
098: /** The log object for this class. */
099: private static final Log LOG = CmsLog.getLog(CmsXmlContent.class);
100:
101: /** Flag to control if auto correction is enabled when saving this XML content. */
102: protected boolean m_autoCorrectionEnabled;
103:
104: /** The XML content definition object (i.e. XML schema) used by this content. */
105: protected CmsXmlContentDefinition m_contentDefinition;
106:
107: /**
108: * Hides the public constructor.<p>
109: */
110: protected CmsXmlContent() {
111:
112: // noop
113: }
114:
115: /**
116: * Creates a new XML content based on the provided XML document.<p>
117: *
118: * The given encoding is used when marshalling the XML again later.<p>
119: *
120: * @param cms the cms context, if <code>null</code> no link validation is performed
121: * @param document the document to create the xml content from
122: * @param encoding the encoding of the xml content
123: * @param resolver the XML entitiy resolver to use
124: */
125: protected CmsXmlContent(CmsObject cms, Document document,
126: String encoding, EntityResolver resolver) {
127:
128: // must set document first to be able to get the content definition
129: m_document = document;
130: // for the next line to work the document must already be available
131: m_contentDefinition = getContentDefinition(resolver);
132: // initialize the XML content structure
133: initDocument(cms, m_document, encoding, m_contentDefinition);
134: }
135:
136: /**
137: * Create a new XML content based on the given default content,
138: * that will have all language nodes of the default content and ensures the presence of the given locale.<p>
139: *
140: * The given encoding is used when marshalling the XML again later.<p>
141: *
142: * @param cms the current users OpenCms content
143: * @param locale the locale to generate the default content for
144: * @param modelUri the absolute path to the XML content file acting as model
145: *
146: * @throws CmsException in case the model file is not found or not valid
147: */
148: protected CmsXmlContent(CmsObject cms, Locale locale,
149: String modelUri) throws CmsException {
150:
151: // init model from given modelUri
152: CmsFile modelFile = cms.readFile(modelUri,
153: CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
154: CmsXmlContent model = CmsXmlContentFactory.unmarshal(cms,
155: modelFile);
156:
157: // initialize macro resolver to use on model file values
158: CmsMacroResolver macroResolver = CmsMacroResolver.newInstance()
159: .setCmsObject(cms);
160:
161: // content defition must be set here since it's used during document creation
162: m_contentDefinition = model.getContentDefinition();
163: // get the document from the default content
164: Document document = (Document) model.m_document.clone();
165: // initialize the XML content structure
166: initDocument(cms, document, model.getEncoding(),
167: m_contentDefinition);
168: // resolve eventual macros in the nodes
169: visitAllValuesWith(new CmsXmlContentMacroVisitor(cms,
170: macroResolver));
171: if (!hasLocale(locale)) {
172: // required locale not present, add it
173: try {
174: addLocale(cms, locale);
175: } catch (CmsXmlException e) {
176: // this can not happen since the locale does not exist
177: }
178: }
179: }
180:
181: /**
182: * Create a new XML content based on the given content definiton,
183: * that will have one language node for the given locale all initialized with default values.<p>
184: *
185: * The given encoding is used when marshalling the XML again later.<p>
186: *
187: * @param cms the current users OpenCms content
188: * @param locale the locale to generate the default content for
189: * @param encoding the encoding to use when marshalling the XML content later
190: * @param contentDefinition the content definiton to create the content for
191: */
192: protected CmsXmlContent(CmsObject cms, Locale locale,
193: String encoding, CmsXmlContentDefinition contentDefinition) {
194:
195: // content defition must be set here since it's used during document creation
196: m_contentDefinition = contentDefinition;
197: // create the XML document according to the content definition
198: Document document = m_contentDefinition.createDocument(cms,
199: this , locale);
200: // initialize the XML content structure
201: initDocument(cms, document, encoding, m_contentDefinition);
202: }
203:
204: /**
205: * @see org.opencms.xml.I_CmsXmlDocument#addLocale(org.opencms.file.CmsObject, java.util.Locale)
206: */
207: public void addLocale(CmsObject cms, Locale locale)
208: throws CmsXmlException {
209:
210: if (hasLocale(locale)) {
211: throw new CmsXmlException(
212: org.opencms.xml.page.Messages
213: .get()
214: .container(
215: org.opencms.xml.page.Messages.ERR_XML_PAGE_LOCALE_EXISTS_1,
216: locale));
217: }
218: // add element node for Locale
219: m_contentDefinition.createLocale(cms, this , m_document
220: .getRootElement(), locale);
221: // re-initialize the bookmarks
222: initDocument(cms, m_document, m_encoding, m_contentDefinition);
223: }
224:
225: /**
226: * Adds a new XML content value for the given element name and locale at the given index position
227: * to this XML content document.<p>
228: *
229: * @param cms the current users OpenCms context
230: * @param path the path to the XML content value element
231: * @param locale the locale where to add the new value
232: * @param index the index where to add the value (relative to all other values of this type)
233: *
234: * @return the created XML content value
235: *
236: * @throws CmsIllegalArgumentException if the given path is invalid
237: * @throws CmsRuntimeException if the element identified by the path already occured {@link I_CmsXmlSchemaType#getMaxOccurs()}
238: * or the given <code>index</code> is invalid (too high).
239: */
240: public I_CmsXmlContentValue addValue(CmsObject cms, String path,
241: Locale locale, int index)
242: throws CmsIllegalArgumentException, CmsRuntimeException {
243:
244: // get the schema type of the requested path
245: I_CmsXmlSchemaType type = m_contentDefinition
246: .getSchemaType(path);
247: if (type == null) {
248: throw new CmsIllegalArgumentException(
249: Messages
250: .get()
251: .container(
252: Messages.ERR_XMLCONTENT_UNKNOWN_ELEM_PATH_SCHEMA_1,
253: path));
254: }
255:
256: Element parentElement;
257: String elementName;
258: CmsXmlContentDefinition contentDefinition;
259: if (CmsXmlUtils.isDeepXpath(path)) {
260: // this is a nested content definition, so the parent element must be in the bookmarks
261: String parentPath = CmsXmlUtils
262: .removeLastXpathElement(path);
263: Object o = getBookmark(parentPath, locale);
264: if (o == null) {
265: throw new CmsIllegalArgumentException(
266: Messages
267: .get()
268: .container(
269: Messages.ERR_XMLCONTENT_UNKNOWN_ELEM_PATH_1,
270: path));
271: }
272: CmsXmlNestedContentDefinition parentValue = (CmsXmlNestedContentDefinition) o;
273: parentElement = parentValue.getElement();
274: elementName = CmsXmlUtils.getLastXpathElement(path);
275: contentDefinition = parentValue
276: .getNestedContentDefinition();
277: } else {
278: // the parent element is the locale element
279: parentElement = getLocaleNode(locale);
280: elementName = CmsXmlUtils.removeXpathIndex(path);
281: contentDefinition = m_contentDefinition;
282: }
283:
284: // read the XML siblings from the parent node
285: List siblings = parentElement.elements(elementName);
286:
287: int insertIndex;
288: if (siblings.size() > 0) {
289:
290: if (siblings.size() >= type.getMaxOccurs()) {
291: // must not allow adding an element if max occurs would be violated
292: throw new CmsRuntimeException(Messages.get().container(
293: Messages.ERR_XMLCONTENT_ELEM_MAXOCCURS_2,
294: elementName, new Integer(type.getMaxOccurs())));
295: }
296:
297: if (index > siblings.size()) {
298: // index position behind last element of the list
299: throw new CmsRuntimeException(Messages.get().container(
300: Messages.ERR_XMLCONTENT_ADD_ELEM_INVALID_IDX_3,
301: new Integer(index),
302: new Integer(siblings.size())));
303: }
304:
305: // check for offset required to append beyond last position
306: int offset = (index == siblings.size()) ? 1 : 0;
307: // get the element from the parent at the selected position
308: Element sibling = (Element) siblings.get(index - offset);
309: // check position of the node in the parent node content
310: insertIndex = sibling.getParent().content()
311: .indexOf(sibling)
312: + offset;
313: } else {
314:
315: if (index > 0) {
316: // since the element does not occur, index must be 0
317: throw new CmsRuntimeException(Messages.get().container(
318: Messages.ERR_XMLCONTENT_ADD_ELEM_INVALID_IDX_2,
319: new Integer(index), elementName));
320: }
321:
322: // check where in the type sequence the type should appear
323: int typeIndex = contentDefinition.getTypeSequence()
324: .indexOf(type);
325: if (typeIndex == 0) {
326: // this is the first type, so we just add at the very first position
327: insertIndex = 0;
328: } else {
329:
330: // create a list of all element names that should occur before the selected type
331: List previousTypeNames = new ArrayList();
332: for (int i = 0; i < typeIndex; i++) {
333: I_CmsXmlSchemaType t = (I_CmsXmlSchemaType) contentDefinition
334: .getTypeSequence().get(i);
335: previousTypeNames.add(t.getName());
336: }
337:
338: // iterate all elements of the parent node
339: Iterator i = parentElement.content().iterator();
340: int pos = 0;
341: while (i.hasNext()) {
342: Node node = (Node) i.next();
343: if (node instanceof Element) {
344: if (!previousTypeNames.contains(node.getName())) {
345: // the element name is NOT in the list of names that occure before the selected type,
346: // so it must be an element that occurs AFTER the type
347: break;
348: }
349: }
350: pos++;
351: }
352: insertIndex = pos;
353: }
354: }
355:
356: // append the new element at the calculated position
357: I_CmsXmlContentValue newValue = addValue(cms, parentElement,
358: type, locale, insertIndex);
359:
360: // re-initialize this XML content
361: initDocument(m_document, m_encoding, m_contentDefinition);
362:
363: // return the value instance that was stored in the bookmarks
364: // just returning "newValue" isn't enough since this instance is NOT stored in the bookmarks
365: return (I_CmsXmlContentValue) getBookmark(getBookmarkName(
366: newValue.getPath(), locale));
367: }
368:
369: /**
370: * @see org.opencms.xml.I_CmsXmlDocument#getContentDefinition()
371: */
372: public CmsXmlContentDefinition getContentDefinition() {
373:
374: return m_contentDefinition;
375: }
376:
377: /**
378: * @see org.opencms.xml.A_CmsXmlDocument#getLinkProcessor(org.opencms.file.CmsObject, org.opencms.staticexport.CmsLinkTable)
379: */
380: public CmsLinkProcessor getLinkProcessor(CmsObject cms,
381: CmsLinkTable linkTable) {
382:
383: // initialize link processor
384: String relativeRoot = null;
385: if (m_file != null) {
386: relativeRoot = CmsResource.getParentFolder(cms
387: .getSitePath(m_file));
388: }
389: return new CmsLinkProcessor(cms, linkTable, getEncoding(),
390: relativeRoot);
391: }
392:
393: /**
394: * Returns the value sequence for the selected element name in this XML content.<p>
395: *
396: * If the given element name is not valid according to the schema of this XML content,
397: * <code>null</code> is returned.<p>
398: *
399: * @param name the element name (XML node name) to the the value sequence for
400: * @param locale the locale to get the value sequence for
401: *
402: * @return the value sequence for the selected element name in this XML content
403: */
404: public CmsXmlContentValueSequence getValueSequence(String name,
405: Locale locale) {
406:
407: I_CmsXmlSchemaType type = m_contentDefinition
408: .getSchemaType(name);
409: if (type == null) {
410: return null;
411: }
412: return new CmsXmlContentValueSequence(name, type, locale, this );
413: }
414:
415: /**
416: * @see org.opencms.xml.A_CmsXmlDocument#isAutoCorrectionEnabled()
417: */
418: public boolean isAutoCorrectionEnabled() {
419:
420: return m_autoCorrectionEnabled;
421: }
422:
423: /**
424: * Removes an existing XML content value of the given element name and locale at the given index position
425: * from this XML content document.<p>
426: *
427: * @param name the name of the XML content value element
428: * @param locale the locale where to remove the value
429: * @param index the index where to remove the value (relative to all other values of this type)
430: */
431: public void removeValue(String name, Locale locale, int index) {
432:
433: // first get the value from the selected locale and index
434: I_CmsXmlContentValue value = getValue(name, locale, index);
435:
436: // check for the min / max occurs constrains
437: List values = getValues(name, locale);
438: if (values.size() <= value.getMinOccurs()) {
439: // must not allow removing an element if min occurs would be violated
440: throw new CmsRuntimeException(Messages.get().container(
441: Messages.ERR_XMLCONTENT_ELEM_MINOCCURS_2, name,
442: new Integer(value.getMinOccurs())));
443: }
444:
445: // detach the value node from the XML document
446: value.getElement().detach();
447:
448: // re-initialize this XML content
449: initDocument(m_document, m_encoding, m_contentDefinition);
450: }
451:
452: /**
453: * Resolves the mappings for all values of this XML content.<p>
454: *
455: * @param cms the current users OpenCms context
456: */
457: public void resolveMappings(CmsObject cms) {
458:
459: // iterate through all initialized value nodes in this XML content
460: CmsXmlContentMappingVisitor visitor = new CmsXmlContentMappingVisitor(
461: cms, this );
462: visitAllValuesWith(visitor);
463: }
464:
465: /**
466: * Sets the flag to control if auto correction is enabled when saving this XML content.<p>
467: *
468: * @param value the flag to control if auto correction is enabled when saving this XML content
469: */
470: public void setAutoCorrectionEnabled(boolean value) {
471:
472: m_autoCorrectionEnabled = value;
473: }
474:
475: /**
476: * @see org.opencms.xml.I_CmsXmlDocument#validate(org.opencms.file.CmsObject)
477: */
478: public CmsXmlContentErrorHandler validate(CmsObject cms) {
479:
480: // iterate through all initialized value nodes in this XML content
481: CmsXmlContentValidationVisitor visitor = new CmsXmlContentValidationVisitor(
482: cms);
483: visitAllValuesWith(visitor);
484:
485: return visitor.getErrorHandler();
486: }
487:
488: /**
489: * Visits all values of this XML content with the given value visitor.<p>
490: *
491: * Please note that the order in which the values are visited may NOT be the
492: * order they apper in the XML document. It is ensured that the the parent
493: * of a nested value is visited before the element it contains.<p>
494: *
495: * @param visitor the value visitor implementation to visit the values with
496: */
497: public void visitAllValuesWith(I_CmsXmlContentValueVisitor visitor) {
498:
499: List bookmarks = new ArrayList(getBookmarks());
500: Collections.sort(bookmarks);
501:
502: for (int i = 0; i < bookmarks.size(); i++) {
503:
504: String key = (String) bookmarks.get(i);
505: I_CmsXmlContentValue value = (I_CmsXmlContentValue) getBookmark(key);
506: visitor.visit(value);
507: }
508: }
509:
510: /**
511: * @see org.opencms.xml.A_CmsXmlDocument#getBookmark(java.lang.String)
512: */
513: protected Object getBookmark(String bookmark) {
514:
515: // allows package classes to directly access the bookmark information of the XML content
516: return super .getBookmark(bookmark);
517: }
518:
519: /**
520: * @see org.opencms.xml.A_CmsXmlDocument#getBookmarks()
521: */
522: protected Set getBookmarks() {
523:
524: // allows package classes to directly access the bookmark information of the XML content
525: return super .getBookmarks();
526: }
527:
528: /**
529: * Returns the XML root element node for the given locale.<p>
530: *
531: * @param locale the locale to get the root element for
532: *
533: * @return the XML root element node for the given locale
534: *
535: * @throws CmsRuntimeException if no language element is found in the document
536: */
537: protected Element getLocaleNode(Locale locale)
538: throws CmsRuntimeException {
539:
540: String localeStr = locale.toString();
541: Iterator i = m_document.getRootElement().elements().iterator();
542: while (i.hasNext()) {
543: Element element = (Element) i.next();
544: if (localeStr
545: .equals(element
546: .attributeValue(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE))) {
547: // language element found, return it
548: return element;
549: }
550: }
551:
552: // language element was not found
553: throw new CmsRuntimeException(Messages.get().container(
554: Messages.ERR_XMLCONTENT_MISSING_LOCALE_1, locale));
555: }
556:
557: /**
558: * Initializes an XML document based on the provided document, encoding and content definition.<p>
559: *
560: * Checks the links and removes invalid ones in the initialized document.<p>
561: *
562: * @param cms the current users OpenCms content
563: * @param document the base XML document to use for initializing
564: * @param encoding the encoding to use when marshalling the document later
565: * @param definition the content definition to use
566: */
567: protected void initDocument(CmsObject cms, Document document,
568: String encoding, CmsXmlContentDefinition definition) {
569:
570: initDocument(document, encoding, definition);
571: // check invalid links
572: if (cms != null) {
573: // this will remove all invalid links
574: getContentDefinition().getContentHandler()
575: .invalidateBrokenLinks(cms, this );
576: }
577: }
578:
579: /**
580: * @see org.opencms.xml.A_CmsXmlDocument#initDocument(org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition)
581: */
582: protected void initDocument(Document document, String encoding,
583: CmsXmlContentDefinition definition) {
584:
585: m_document = document;
586: m_contentDefinition = definition;
587: m_encoding = CmsEncoder.lookupEncoding(encoding, encoding);
588: m_elementLocales = new HashMap();
589: m_elementNames = new HashMap();
590: m_locales = new HashSet();
591: clearBookmarks();
592:
593: // initialize the bookmarks
594: for (Iterator i = m_document.getRootElement().elementIterator(); i
595: .hasNext();) {
596: Element node = (Element) i.next();
597: try {
598: Locale locale = CmsLocaleManager
599: .getLocale(node
600: .attribute(
601: CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE)
602: .getValue());
603:
604: addLocale(locale);
605: processSchemaNode(node, null, locale, definition);
606: } catch (NullPointerException e) {
607: LOG.error(Messages.get().getBundle().key(
608: Messages.LOG_XMLCONTENT_INIT_BOOKMARKS_0), e);
609: }
610: }
611:
612: }
613:
614: /**
615: * Sets the file this XML content is written to.<p>
616: *
617: * @param file the file this XML content content is written to
618: */
619: protected void setFile(CmsFile file) {
620:
621: m_file = file;
622: }
623:
624: /**
625: * Adds a new XML schema type with the default value to the given parent node.<p>
626: *
627: * @param cms the cms context
628: * @param parent the XML parent element to add the new value to
629: * @param type the type of the value to add
630: * @param locale the locale to add the new value for
631: * @param insertIndex the index in the XML document where to add the XML node
632: *
633: * @return the created XML content value
634: */
635: private I_CmsXmlContentValue addValue(CmsObject cms,
636: Element parent, I_CmsXmlSchemaType type, Locale locale,
637: int insertIndex) {
638:
639: // first generate the XML element for the new value
640: Element element = type.generateXml(cms, this , parent, locale);
641: // detatch the XML element from the appended position in order to insert it at the required position
642: element.detach();
643: // add the XML element at the required position in the parent XML node
644: parent.content().add(insertIndex, element);
645: // create the type and return it
646: I_CmsXmlContentValue value = type.createValue(this , element,
647: locale);
648: // generate the default value again - required for nested mappings because only now the full path is available
649: String defaultValue = m_contentDefinition.getContentHandler()
650: .getDefault(cms, value, locale);
651: if (defaultValue != null) {
652: // only if there is a default value available use it to overwrite the initial default
653: value.setStringValue(cms, defaultValue);
654: }
655: // finally return the value
656: return value;
657: }
658:
659: /**
660: * Returns the content definition object for this xml content object.<p>
661: *
662: * @param resolver the XML entity resolver to use, required for VFS access
663: *
664: * @return the content definition object for this xml content object
665: *
666: * @throws CmsRuntimeException if the schema location attribute (<code>systemId</code>)cannot be found,
667: * parsing of the schema fails, an underlying IOException occurs or unmarshalling fails
668: *
669: */
670: private CmsXmlContentDefinition getContentDefinition(
671: EntityResolver resolver) throws CmsRuntimeException {
672:
673: String schemaLocation = m_document
674: .getRootElement()
675: .attributeValue(
676: I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION);
677: // Note regarding exception handling:
678: // Since this object already is a valid XML content object,
679: // it must have a valid schema, otherwise it would not exist.
680: // Therefore the exceptions should never be really thrown.
681: if (schemaLocation == null) {
682: throw new CmsRuntimeException(Messages.get().container(
683: Messages.ERR_XMLCONTENT_MISSING_SCHEMA_0));
684: }
685:
686: try {
687: return CmsXmlContentDefinition.unmarshal(schemaLocation,
688: resolver);
689: } catch (SAXException e) {
690: throw new CmsRuntimeException(Messages.get().container(
691: Messages.ERR_XML_SCHEMA_PARSE_0), e);
692: } catch (IOException e) {
693: throw new CmsRuntimeException(Messages.get().container(
694: Messages.ERR_XML_SCHEMA_IO_0), e);
695: } catch (CmsXmlException e) {
696: throw new CmsRuntimeException(Messages.get().container(
697: Messages.ERR_XMLCONTENT_UNMARSHAL_0), e);
698: }
699: }
700:
701: /**
702: * Processes a document node and extracts the values of the node according to the provided XML
703: * content definition.<p>
704: *
705: * @param root the root node element to process
706: * @param rootPath the Xpath of the root node in the document
707: * @param locale the locale
708: * @param definition the XML content definition to use for processing the values
709: */
710: private void processSchemaNode(Element root, String rootPath,
711: Locale locale, CmsXmlContentDefinition definition) {
712:
713: int count = 1;
714: String previousName = null;
715:
716: // first remove all non-element node (i.e. white space text nodes)
717: List content = root.content();
718: for (int i = content.size() - 1; i >= 0; i--) {
719: Node node = (Node) content.get(i);
720: if (!(node instanceof Element)) {
721: // this node is not an element, so it must be a white space text node, remove it
722: content.remove(i);
723: }
724: }
725:
726: // iterate all elements again
727: for (Iterator i = root.content().iterator(); i.hasNext();) {
728:
729: // node must be an element since all non-elements were removed
730: Element element = (Element) i.next();
731:
732: // check if this is a new node, if so reset the node counter
733: String name = element.getName();
734: if ((previousName == null) || !previousName.equals(name)) {
735: previousName = name;
736: count = 1;
737: }
738:
739: // build the Xpath expression for the current node
740: String path;
741: if (rootPath != null) {
742: StringBuffer b = new StringBuffer(rootPath.length()
743: + name.length() + 6);
744: b.append(rootPath);
745: b.append('/');
746: b.append(CmsXmlUtils.createXpathElement(name, count));
747: path = b.toString();
748: } else {
749: path = CmsXmlUtils.createXpathElement(name, count);
750: }
751:
752: // create a XML content value element
753: I_CmsXmlSchemaType schemaType = definition
754: .getSchemaType(name);
755:
756: if (schemaType != null) {
757: // directly add simple type to schema
758: I_CmsXmlContentValue value = schemaType.createValue(
759: this , element, locale);
760: addBookmark(path, locale, true, value);
761:
762: if (!schemaType.isSimpleType()) {
763: // recurse for nested schema
764: CmsXmlNestedContentDefinition nestedSchema = (CmsXmlNestedContentDefinition) schemaType;
765: processSchemaNode(element, path, locale,
766: nestedSchema.getNestedContentDefinition());
767: }
768: } else {
769: // unknown XML node name according to schema
770: if (LOG.isWarnEnabled()) {
771: LOG.warn(Messages.get().getBundle().key(
772: Messages.LOG_XMLCONTENT_INVALID_ELEM_2,
773: name, definition.getSchemaLocation()));
774: }
775: }
776:
777: // increase the node counter
778: count++;
779: }
780: }
781: }
|