/** * Copyright 2004-2016 Riccardo Solmi. All rights reserved. * This file is part of the Whole Platform. * * The Whole Platform is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Whole Platform is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>. */ package org.whole.lang.xml.builders; import static org.whole.lang.xml.reflect.XmlEntityDescriptorEnum.*; import org.whole.lang.bindings.IBindingManager; import org.whole.lang.builders.GenericEventTrackingBuilder; import org.whole.lang.builders.GenericIdentityBuilder; import org.whole.lang.builders.IBuilder; import org.whole.lang.builders.IBuilderOperation; import org.whole.lang.builders.ModelBuilderOperation; import org.whole.lang.contexts.IdentityEntityContext; import org.whole.lang.reflect.EntityDescriptor; import org.whole.lang.xml.reflect.XmlLanguageKit; import org.whole.lang.xml.util.XmlUtils; /** * An Xml Builder that performs several normalization steps in streaming: * * 1. Introduces implied Content events * 2. Removes nested Content events (ie Content_(); Content_();) * 3. packs sequences of CDataSect and CharData * 4. optionally merges CDataSect and CharData in a single CharData * * @author Enrico Persiani */ public class XmlNormalizerBuilder extends XmlSpecificBuilderAdapter { protected IBuilderOperation op; public XmlNormalizerBuilder(IBuilderOperation op, IBindingManager bm) { super(new GenericIdentityBuilder(), new IdentityEntityContext()); this.op = op; wSetBuilderStrategy(new GenericXmlNormalizerBuilder(getTargetBuilder(), bm.wIsSet("mergeCDataSect") ? bm.wBooleanValue("mergeCDataSect") : false)); } protected IBuilder targetBuilder; protected IBuilder getTargetBuilder() { if (targetBuilder == null) targetBuilder = ModelBuilderOperation.ID.equals(op.wGetOperationId()) ? op.wGetBuilder(XmlLanguageKit.URI, false) : //FIXME workaround, should use a generic builder op.wGetBuilder(); return targetBuilder; } protected class GenericXmlNormalizerBuilder extends GenericEventTrackingBuilder { protected boolean mergeCDataSect; protected boolean hasCharData; protected StringBuilder charData; protected StringBuilder cdataSectData; public GenericXmlNormalizerBuilder(IBuilder builder, boolean mergeCDataSect) { super(builder); this.mergeCDataSect = mergeCDataSect; this.hasCharData = false; this.charData = new StringBuilder(); this.cdataSectData = new StringBuilder(); } protected void appendCharData(String s) { appendCharData(s, false); } protected void appendCharData(String s, boolean force) { if (force || !XmlUtils.isIgnorableWhitespace(s)) { charData.append(s); hasCharData = true; } } protected void appendCDataSectData(String s) { cdataSectData.append(s); } protected void generateCharData() { if (hasCharData) { super.wEntity(CharData, charData.toString()); charData.setLength(0); hasCharData = false; } } protected void generateCDataSect() { if (mergeCDataSect) appendCharData(cdataSectData.toString(), true); else { generateCharData(); super.wEntity_(CDataSect, 1); super.wEntity(CDataSectData, cdataSectData.toString()); super._wEntity(CDataSect); } cdataSectData.setLength(0); } @Override public void wEntity(EntityDescriptor<?> ed, String value) { if (CharData.equals(ed)) { if (!Content.equals(wGetEntityDescriptor())) { //NOTE: no parent Content means single CharData super.wEntity_(Content, 1); if (!XmlUtils.isIgnorableWhitespace(value)) super.wEntity(ed, value); super._wEntity(Content); } else appendCharData(value); } else if (CDataSectData.equals(ed)) appendCDataSectData(value); else super.wEntity(ed, value); } @Override public void wEntity_(EntityDescriptor<?> ed) { if (IContent.isLanguageSupertypeOf(ed)) generateCharData(); switch (ed.getOrdinal()) { case Element_ord: case PI_ord: case Comment_ord: EntityDescriptor<?> context = wGetEntityDescriptor(); // NOTE: wGetEntityDescriptor() == null when we reach UNKNOWN state if (context != null && !Content.equals(context) && !Document.equals(context) && !Misc.equals(context)) builderStrategy.wEntity_(Content, 1); break; case Content_ord: if (Content.equals(wGetEntityDescriptor())) { pushUnknownState(); return; // skip repeated Content } break; case CDataSect_ord: if (!Content.equals(wGetEntityDescriptor())) builderStrategy.wEntity_(Content, 1); appendCDataSectData(""); return; } super.wEntity_(ed); } @Override public void wEntity_(EntityDescriptor<?> ed, int initialCapacity) { wEntity_(ed); } @Override public void _wEntity(EntityDescriptor<?> ed) { switch (ed.getOrdinal()) { case CDataSect_ord: generateCDataSect(); if (!Content.equals(wGetEntityDescriptor())) { generateCharData(); builderStrategy._wEntity(Content); } return; case Element_ord: case PI_ord: case Comment_ord: super._wEntity(ed); EntityDescriptor<?> context = wGetEntityDescriptor(); // NOTE: wGetEntityDescriptor() == null when we reach UNKNOWN state if (context != null && !Content.equals(context) && !Document.equals(context) && !Misc.equals(context)) builderStrategy._wEntity(Content); return; case Content_ord: generateCharData(); if (!Content.equals(wGetEntityDescriptor())) { // skip repeated Content // NOTE: wGetEntityDescriptor() == null when we reach UNKNOWN state popState(); return; } } super._wEntity(ed); } } }