/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk 2008 Martin Fleurke, Alex Buloichik, Didier Briel 2009 Didier Briel 2010 Antonio Vilei 2011 Didier Briel 2013 Didier Briel, Alex Buloichik Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.filters3.xml; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import org.omegat.core.Core; import org.omegat.core.data.ProtectedPart; import org.omegat.filters2.FilterContext; import org.omegat.filters2.TranslationException; import org.omegat.filters3.Attribute; import org.omegat.filters3.Element; import org.omegat.filters3.Entry; import org.omegat.filters3.Tag; import org.omegat.util.OStrings; import org.omegat.util.StringUtil; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.ext.DeclHandler; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.DefaultHandler; /** * The part of XML filter that actually does the job. This class is called back * by SAXParser. * * Entities described on * http://www.ibm.com/developerworks/xml/library/x-entities/ * http://xmlwriter.net/xml_guide/entity_declaration.shtml * * @author Maxym Mykhalchuk * @author Martin Fleurke * @author Didier Briel * @author Alex Buloichik (alex73mail@gmail.com) */ public class Handler extends DefaultHandler implements LexicalHandler, DeclHandler { private Translator translator; private XMLDialect dialect; private File inFile; private File outFile; private FilterContext context; /** Main file writer to write translated text to. */ private BufferedWriter mainWriter; /** Current writer for an external included file. */ private BufferedWriter extWriter = null; /** Current path in XML. */ private final Stack<String> currentTagPath = new Stack<String>(); /** * Returns current writer we should write into. If we're in main file, * returns {@link #mainWriter}, else (if we're writing external file) * returns {@link #extWriter}. */ private BufferedWriter currWriter() { if (extWriter != null) { return extWriter; } else { return mainWriter; } } /** Currently parsed external entity that has its own writer. */ private Entity extEntity = null; /** Current entry that collects normal text. */ Entry entry; /** Stack of entries that collect out-of-turn text. */ Stack<Entry> outofturnEntries = new Stack<Entry>(); /** Current entry that collects the text surrounded by intact tag. */ Entry intacttagEntry = null; /** Keep the attributes of an intact tag. */ org.omegat.filters3.Attributes intacttagAttributes = null; /** Keep the attributes of paragraph tags. */ Stack<org.omegat.filters3.Attributes> paragraphTagAttributes = new Stack<org.omegat.filters3.Attributes>(); /** Keep the attributes of preformat tags. */ Stack<org.omegat.filters3.Attributes> preformatTagAttributes = new Stack<org.omegat.filters3.Attributes>(); /** Keep the attributes of xml tags. */ Stack<org.omegat.filters3.Attributes> xmlTagAttributes = new Stack<org.omegat.filters3.Attributes>(); /** Current entry that collects the text surrounded by intact tag. */ String intacttagName = null; /** Names of possible paragraph tags. */ Stack<String> paragraphTagName = new Stack<String>(); /** Names of possible preformat tags. */ Stack<String> preformatTagName = new Stack<String>(); /** Name of the current variable translatable tag */ Stack<String> translatableTagName = new Stack<String>(); /** Names of xml tags. */ Stack<String> xmlTagName = new Stack<String>(); /** Status of the xml:space="preserve" flag */ private boolean spacePreserve = false; /** Now we collect out-of-turn entry. */ private boolean collectingOutOfTurnText() { return !outofturnEntries.empty(); } /** Now we collect intact text. */ private boolean collectingIntactText() { return intacttagEntry != null; } private boolean isTranslatableTag() { return !translatableTagName.empty(); } private boolean isSpacePreservingTag() { if (Core.getFilterMaster().getConfig().isPreserveSpaces()) { // Preserve spaces for all tags return true; } else { return spacePreserve; } } private void resetSpacePreservingTag() { spacePreserve = false; } /** * Returns current entry we collect text into. If we collect normal text, * returns {@link #entry}, else returns the last of * {@link #outofturnEntries}. */ private Entry currEntry() { if (collectingIntactText()) { return intacttagEntry; } else if (collectingOutOfTurnText()) { return outofturnEntries.peek(); } else { return entry; } } /** * External entities declared in source file. Each entry is of type * {@link Entity}. */ private List<Entity> externalEntities = new ArrayList<Entity>(); /** * Internal entities declared in source file. A {@link Map} from * {@link String}/entity name/ to {@link Entity}. */ private Map<String, Entity> internalEntities = new HashMap<String, Entity>(); /** Internal entity just started. */ private Entity internalEntityStarted = null; /** Currently collected text is wrapped in CDATA section. */ private boolean inCDATA = false; /** Whether we're curren */ // private boolean inPreformattingTag = false; /** * SAX parser encountered DTD declaration, so probably it will parse DTD * next, but some nice things may happen before. */ private DTD dtd = null; /** SAX parser parses DTD -- we don't extract translatable text from there */ private boolean inDTD = false; /** * External files this handler has processed, because they were included * into main file. Each entry is of type {@link File}. */ private List<File> processedFiles = new ArrayList<File>(); /** * Returns external files this handler has processed, because they were * included into main file. Each entry is {@link File}. */ public List<File> getProcessedFiles() { return processedFiles.isEmpty() ? null : processedFiles; } /** Throws a nice error message when SAX parser encounders fastal error. */ private void reportFatalError(SAXParseException e) throws SAXException, MalformedURLException, URISyntaxException { int linenum = e.getLineNumber(); String filename; if (e.getSystemId() != null) { File errorfile = new File(inFile.getParentFile(), localizeSystemId(e.getSystemId())); if (errorfile.exists()) { filename = errorfile.getAbsolutePath(); } else { filename = inFile.getAbsolutePath(); } } else { filename = inFile.getAbsolutePath(); } throw new SAXException("\n" + StringUtil.format(e.getMessage() + "\n" + OStrings.getString("XML_FATAL_ERROR"), filename, linenum)); } /** * Creates a new instance of Handler */ public Handler(Translator translator, XMLDialect dialect, File inFile, File outFile, FilterContext fc) throws IOException { this.translator = translator; this.dialect = dialect; this.inFile = inFile; this.outFile = outFile; this.context = fc; this.mainWriter = translator.createWriter(outFile, fc.getOutEncoding()); } public FilterContext getContext() { return context; } private static final String START_JARSCHEMA = "jar:"; private static final String START_FILESCHEMA = "file:"; // //////////////////////////////////////////////////////////////////////// // Utility methods // //////////////////////////////////////////////////////////////////////// private String sourceFolderAbsolutePath = null; /** * Returns source folder of the main file with trailing '/' * (File.separator). */ private String getSourceFolderAbsolutePath() { if (sourceFolderAbsolutePath == null) { String res = inFile.getAbsoluteFile().getParent(); try { res = inFile.getCanonicalFile().getParent(); } catch (IOException ex) { } if (res.codePointBefore(res.length()) != File.separatorChar) { res = res + File.separatorChar; } sourceFolderAbsolutePath = res; } return sourceFolderAbsolutePath; } /** Makes System ID not an absolute, but a relative one. */ private String localizeSystemId(String systemId) throws URISyntaxException, MalformedURLException { if (systemId.startsWith(START_FILESCHEMA)) { File thisOutFile = new File(new URL(systemId).toURI()); String thisOutPath = thisOutFile.getAbsolutePath(); if (thisOutPath.startsWith(getSourceFolderAbsolutePath())) { return thisOutPath.substring(getSourceFolderAbsolutePath().length()); } } return systemId; } /** Whether the file with given systemId is in source folder. */ private boolean isInSource(String systemId) throws URISyntaxException, MalformedURLException { if (systemId.startsWith(START_FILESCHEMA)) { File thisOutFile = new File(new URL(systemId).toURI()); if (thisOutFile.getAbsolutePath().startsWith(getSourceFolderAbsolutePath())) { return true; } } return false; } /** Finds external entity by publicId and systemId. */ private Entity findExternalEntity(String publicId, String systemId) { if (publicId == null && systemId == null) { return null; } for (Entity entity : externalEntities) { if (entity.isInternal()) { continue; } if (StringUtil.equal(publicId, entity.getPublicId()) && StringUtil.equal(systemId, entity.getSystemId())) { return entity; } } return null; } /** * Is called when the entity starts. Tries to find out whether it's an * internal entity, and if so, turns on the trigger to queue entity, and not * the text it represents, in {@link #characters(char[],int,int)}. */ private void doStartEntity(String name) { if (inDTD) { return; } internalEntityStarted = internalEntities.get(name); } /** * Is called when the entity is ended. Tries to find out whether it's an * external entity we created a writer for, and if so, closes the writer and * nulls the entity. */ private void doEndEntity(String name) throws SAXException, TranslationException, IOException { if (inDTD || extEntity == null) { return; } if (extEntity.getOriginalName().equals(name)) { boolean parameterEntiry = extEntity.isParameter(); extEntity = null; translateAndFlush(); extWriter.close(); extWriter = null; if (parameterEntiry) { mainWriter.write(name + ';'); } else { mainWriter.write('&' + name + ';'); } } } /** * Resolves external entity and creates a new writer if it's an included * file. */ public InputSource doResolve(String publicId, String systemId) throws SAXException, TranslationException, IOException, URISyntaxException { if (dtd != null && StringUtil.equal(publicId, dtd.getPublicId()) && (StringUtil.equal(systemId, dtd.getSystemId()) || StringUtil.equal( localizeSystemId(systemId), dtd.getSystemId()))) { inDTD = true; } if (systemId != null && (systemId.startsWith(START_JARSCHEMA) || systemId.startsWith(START_FILESCHEMA))) { InputSource entity = new InputSource(systemId); // checking if f if (systemId.startsWith(START_FILESCHEMA)) { if (!new File(new URI(systemId)).exists()) { entity = null; } } if (entity != null) { if (!inDTD && outFile != null && isInSource(systemId) && extEntity == null) { extEntity = findExternalEntity(publicId, localizeSystemId(systemId)); if (extEntity != null) { // if we resolved a new entity, and: // 1. it's not a DTD // 2. it's in project's source folder // 3. it's not during project load // then it's an external file, and we need to // write it as an external file translateAndFlush(); File extFile = new File(outFile.getParentFile(), localizeSystemId(systemId)); processedFiles.add(new File(inFile.getParent(), localizeSystemId(systemId))); extWriter = translator.createWriter(extFile, context.getOutEncoding()); extWriter.write("<?xml version=\"1.0\"?>\n"); } } return entity; } else { return new InputSource(new java.io.StringReader("")); } } else { InputSource source = dialect.resolveEntity(publicId, systemId); if (source != null) { return source; } else { return new InputSource(new java.io.StringReader("")); } } } private void queueText(String s) { if (!translator.isInIgnored()) { translator.text(s); } // TODO: ideally, xml:space=preserved would be handled at this level, but that would suppose // knowing here whether we're inside a preformatted tag, etc. if (internalEntityStarted != null && s.equals(internalEntityStarted.getValue())) { currEntry().add(new XMLEntityText(internalEntityStarted)); } else { boolean added = false; if (!currEntry().isEmpty()) { Element elem = currEntry().get(currEntry().size() - 1); if (elem instanceof XMLText) { XMLText text = (XMLText) elem; if (text.isInCDATA() == inCDATA) { currEntry().resetTagDetected(); text.append(s); added = true; } } } if (!added) { currEntry().add(new XMLText(s, inCDATA)); } } } private void queueTag(String tag, Attributes attributes) { Tag xmltag = null; XMLIntactTag intacttag = null; setTranslatableTag(tag, XMLUtils.convertAttributes(attributes)); setSpacePreservingTag(XMLUtils.convertAttributes(attributes)); if (!collectingIntactText()) { if (isContentBasedTag(tag, XMLUtils.convertAttributes(attributes))) { intacttag = new XMLContentBasedTag(dialect, this, tag, getShortcut(tag), dialect .getContentBasedTags().get(tag), attributes); xmltag = intacttag; intacttagName = tag; intacttagAttributes = XMLUtils.convertAttributes(attributes); } else if (isIntactTag(tag, XMLUtils.convertAttributes(attributes))) { intacttag = new XMLIntactTag(dialect, this, tag, getShortcut(tag), attributes); xmltag = intacttag; intacttagName = tag; intacttagAttributes = XMLUtils.convertAttributes(attributes); } } if (xmltag == null) { xmltag = new XMLTag(tag, getShortcut(tag), Tag.Type.BEGIN, attributes, this.translator.getTargetLanguage()); xmlTagName.push(xmltag.getTag()); xmlTagAttributes.push(xmltag.getAttributes()); } currEntry().add(xmltag); if (intacttag != null) { intacttagEntry = intacttag.getIntactContents(); } if (!collectingIntactText()) { for (int i = 0; i < xmltag.getAttributes().size(); i++) { Attribute attr = xmltag.getAttributes().get(i); if ((dialect.getTranslatableAttributes().contains(attr.getName()) || dialect .getTranslatableTagAttributes().containsPair(tag, attr.getName())) && dialect.validateTranslatableTagAttribute(tag, attr.getName(), xmltag.getAttributes())) { attr.setValue(StringUtil.makeValidXML( translator.translate(StringUtil.unescapeXMLEntities(attr.getValue()), null))); } } } } /** * Queue tag that should be ignored by editor, including content and all subtags. */ private void queueIgnoredTag(String tag, Attributes attributes) { Tag xmltag = null; setSpacePreservingTag(XMLUtils.convertAttributes(attributes)); if (xmltag == null) { xmltag = new XMLTag(tag, getShortcut(tag), Tag.Type.BEGIN, attributes, this.translator.getTargetLanguage()); xmlTagName.push(xmltag.getTag()); xmlTagAttributes.push(xmltag.getAttributes()); } currEntry().add(xmltag); } private void queueEndTag(String tag) { int len = currEntry().size(); if (len > 0 && (currEntry().get(len - 1) instanceof XMLTag) && (((XMLTag) currEntry().get(len - 1)).getTag().equals(tag) && ((XMLTag) currEntry().get( len - 1)).getType() == Tag.Type.BEGIN) && !isClosingTagRequired()) { if (((XMLTag) currEntry().get(len - 1)).getTag().equals(xmlTagName.lastElement())) { xmlTagName.pop(); xmlTagAttributes.pop(); } ((XMLTag) currEntry().get(len - 1)).setType(Tag.Type.ALONE); } else { XMLTag xmltag = new XMLTag(tag, getShortcut(tag), Tag.Type.END, null, this.translator.getTargetLanguage()); if (xmltag.getTag().equals(xmlTagName.lastElement())) { xmlTagName.pop(); xmltag.setStartAttributes(xmlTagAttributes.pop()); // Restore attributes } currEntry().add(xmltag); } } private void queueComment(String comment) { if (!translator.isInIgnored()) { translator.comment(comment); } currEntry().add(new Comment(comment)); } private void queueProcessingInstruction(String data, String target) { currEntry().add(new ProcessingInstruction(data, target)); } private void queueDTD(DTD dtd) { currEntry().add(dtd); } /** Is called when the tag is started. */ private void start(String tag, Attributes attributes) throws SAXException, TranslationException { boolean prevIgnored = translator.isInIgnored(); translatorTagStart(tag, attributes); if (!translator.isInIgnored()) { if (isOutOfTurnTag(tag)) { XMLOutOfTurnTag ootTag = new XMLOutOfTurnTag(dialect, this, tag, getShortcut(tag), attributes); currEntry().add(ootTag); outofturnEntries.push(ootTag.getEntry()); } else { if (isParagraphTag(tag, XMLUtils.convertAttributes(attributes)) && !collectingOutOfTurnText() && !collectingIntactText()) { translateAndFlush(); } queueTag(tag, attributes); } } else { if (!prevIgnored) { // start ignored from this tags - need to flush translation translateAndFlush(); } queueIgnoredTag(tag, attributes); } } /** Is called when the tag is ended. */ private void end(String tag) throws SAXException, TranslationException { boolean prevIgnored = translator.isInIgnored(); if (!translator.isInIgnored()) { if (collectingIntactText() && tag.equals(intacttagName) && (isIntactTag(tag, null) || isContentBasedTag(tag, null))) { intacttagEntry = null; intacttagName = null; intacttagAttributes = null; removeTranslatableTag(); } else if (collectingOutOfTurnText() && isOutOfTurnTag(tag)) { translateButDontFlash(); outofturnEntries.pop(); } else { queueEndTag(tag); // TODO: If a file doesn't contain any paragraph tag, // the translatable content will be lost if (isParagraphTag(tag) && !collectingOutOfTurnText() && !collectingIntactText()) { translateAndFlush(); } removeTranslatableTag(); } } else { queueEndTag(tag); } translatorTagEnd(tag); if (!translator.isInIgnored() && prevIgnored) { // stop ignored from this tag - need to flush without translate flushButDontTranslate(); } } /** * One of the main methods of the XML filter: it collects all the data, * adjusts it, and sends for translation. * * @see #translateAndFlush() */ private void translateButDontFlash() throws TranslationException { if (currEntry().isEmpty()) { return; } List<ProtectedPart> shortcutDetails = new ArrayList<ProtectedPart>(); boolean tagsAggregation = isTagsAggregationEnabled(); String src = currEntry().sourceToShortcut(tagsAggregation, dialect, shortcutDetails); Element lead = currEntry().get(0); String translation = src; if ((lead instanceof Tag) && (isPreformattingTag(((Tag) lead).getTag(), ((Tag) lead).getAttributes()) || isSpacePreservingTag()) && isTranslatableTag() && !StringUtil.isEmpty(src)) { resetSpacePreservingTag(); translation = translator.translate(src, shortcutDetails); } else { String compressed = src; if (Core.getFilterMaster().getConfig().isRemoveSpacesNonseg()) { compressed = StringUtil.compressSpaces(src); } if (isTranslatableTag()) { translation = translator.translate(compressed, shortcutDetails); } // untranslated is written out uncompressed if (compressed.equals(translation)) { translation = src; } } currEntry().setTranslation(translation, dialect, new ArrayList<ProtectedPart>()); } /** * One of the main methods of the XML filter: it collects all the data, * adjusts it, sends for translation, writes out the translated data and * clears the entry. * * @see #translateButDontFlash() */ private void translateAndFlush() throws SAXException, TranslationException { translateButDontFlash(); try { currWriter().write(currEntry().translationToOriginal()); } catch (IOException e) { throw new SAXException(e); } currEntry().clear(); } /** * Write tag's content without translation. Used for ignored tags. */ private void flushButDontTranslate() throws SAXException, TranslationException { try { currWriter().write(currEntry().translationToOriginal()); } catch (IOException e) { throw new SAXException(e); } currEntry().clear(); } // ///////////////////////////////////////////////////////////////////////// // Dialect Helper methods // ///////////////////////////////////////////////////////////////////////// /** * Returns whether the tag starts a new paragraph. Preformatting tags are * also considered to start a new paragraph, so if * {@link #isPreformattingTag(String)} returns true, this method will also * return true. */ private boolean isParagraphTag(String tag, org.omegat.filters3.Attributes atts) { paragraphTagName.push(tag); paragraphTagAttributes.push(atts); preformatTagName.push(tag); preformatTagAttributes.push(atts); if ((dialect.getParagraphTags() != null && dialect.getParagraphTags().contains(tag)) || isPreformattingTag(tag, atts)) { return true; } else { return dialect.validateParagraphTag(tag, atts); } } /** * Returns whether the tag starts a new paragraph. It is called at the end * of an element (</mrk>), and thus doesn't provide attributes. Those * are restored from the paragraphTagAttributes stack. * * @param tag * A tag * @return <code>true</code> or <code>false</false> */ private boolean isParagraphTag(String tag) { if ((dialect.getParagraphTags() != null && dialect.getParagraphTags().contains(tag)) || isPreformattingTag(tag)) { return true; } else { org.omegat.filters3.Attributes atts = null; if (tag.equals(paragraphTagName.lastElement())) { paragraphTagName.pop(); atts = paragraphTagAttributes.pop(); // Restore attributes } return dialect.validateParagraphTag(tag, atts); } } /** * Returns whether the tag starts a new paragraph. */ public boolean isParagraphTag(Tag tag) { if ((dialect.getParagraphTags() != null && dialect.getParagraphTags().contains(tag.getTag())) || isPreformattingTag(tag.getTag(), tag.getAttributes())) { return true; } else if (tag.getType() == Tag.Type.END && isPreformattingTag(tag.getTag(), tag.getStartAttributes())) { return true; } else { return dialect.validateParagraphTag(tag.getTag(), tag.getAttributes()); } } /** * Returns whether the tag is content based. * * @param tag * A tag * @return <code>true</code> or <code>false</false> */ private boolean isContentBasedTag(String tag, org.omegat.filters3.Attributes atts) { if (dialect.getContentBasedTags() != null && dialect.getContentBasedTags().containsKey(tag)) { return true; } else { if (atts == null) { if (tag.equals(intacttagName)) { atts = intacttagAttributes; // Restore attributes } } return dialect.validateContentBasedTag(tag, atts); } } /** * Returns whether the tag surrounds preformatted block of text. * * @param tag * A tag * @return <code>true</code> or <code>false</false> */ private boolean isPreformattingTag(String tag, org.omegat.filters3.Attributes atts) { if (dialect.getPreformatTags() != null && dialect.getPreformatTags().contains(tag)) { return true; } else { return dialect.validatePreformatTag(tag, atts); } } /** * Returns whether the tag surrounds preformatted block of text. It is * called at the end of an element (</mrk>), and thus doesn't provide * attributes. Those are restored from the preformatTagAttributes stack. * * @param tag * A tag * @return <code>true</code> or <code>false</false> */ private boolean isPreformattingTag(String tag) { if (dialect.getPreformatTags() != null && dialect.getPreformatTags().contains(tag)) { return true; } else { org.omegat.filters3.Attributes atts = null; if (tag.equals(preformatTagName.lastElement())) { preformatTagName.pop(); atts = preformatTagAttributes.pop(); // Restore attributes } return dialect.validatePreformatTag(tag, atts); } } /** * Returns whether the tag surrounds intact block of text which we shouldn't * translate. */ private boolean isIntactTag(String tag, org.omegat.filters3.Attributes atts) { if (dialect.getIntactTags() != null && dialect.getIntactTags().contains(tag)) { return true; } else { if (atts == null) { if (tag.equals(intacttagName)) { atts = intacttagAttributes; // Restore attributes } } return dialect.validateIntactTag(tag, atts); } } /** * If we are not inside a translatable tag, and if the dialect says the new * one is translatable, add the new tag to the stack * * @param tag * The current opening tag * @param atts * The attributes of the current tag */ // TODO: The concept works only perfectly if the first tag with // translatable content inside the translatable tag is a paragraph // tag void setTranslatableTag(String tag, org.omegat.filters3.Attributes atts) { if (!isTranslatableTag()) { // If stack is empty if (dialect.validateTranslatableTag(tag, atts)) { translatableTagName.push(tag); } } else { translatableTagName.push(tag); } } /** * Remove a tag from the stack of translatable tags */ void removeTranslatableTag() { if (isTranslatableTag()) { // If there is something in the stack translatableTagName.pop(); // Remove it } } private void translatorTagStart(String tag, Attributes atts) { currentTagPath.push(tag); translator.tagStart(constructCurrentPath(), atts); } private void translatorTagEnd(String tag) { translator.tagEnd(constructCurrentPath()); while (!currentTagPath.pop().equals(tag)) { } } private String constructCurrentPath() { StringBuilder path = new StringBuilder(256); for (String t : currentTagPath) { path.append('/').append(t); } return path.toString(); } /** * If the space-preserving flag is not set, and the attributes say it is one, set it * * @param atts * The attributes of the current tag */ private void setSpacePreservingTag(org.omegat.filters3.Attributes atts) { if (isSpacePreservingSet(atts)) { spacePreserve = true; } } private boolean isClosingTagRequired() { return dialect.getClosingTagRequired(); } private boolean isTagsAggregationEnabled() { return dialect.getTagsAggregationEnabled(); } /** Returns whether we face out of turn tag we should collect separately. */ private boolean isOutOfTurnTag(String tag) { return dialect.getOutOfTurnTags() != null && dialect.getOutOfTurnTags().contains(tag); } /** Returns a shortcut for a tag. Queries dialect first, else returns null. */ private String getShortcut(String tag) { if (dialect.getShortcuts() != null) { return dialect.getShortcuts().get(tag); } else { return null; } } /** * Checks whether the xml:space="preserve" attribute is present * @param currentAttributes The current Attributes * @return true or false */ private boolean isSpacePreservingSet(org.omegat.filters3.Attributes currentAttributes) { if (dialect.getForceSpacePreserving()) { return true; } boolean preserve = false; for (int i = 0; i < currentAttributes.size(); i++) { Attribute oneAttribute = currentAttributes.get(i); if ((oneAttribute.getName().equalsIgnoreCase("xml:space") && oneAttribute.getValue().equalsIgnoreCase("preserve"))) { preserve = true; } } return preserve; } // //////////////////////////////////////////////////////////////////////// // Callback methods // //////////////////////////////////////////////////////////////////////// /** * Resolves an external entity. */ @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException { try { return doResolve(publicId, systemId); } catch (URISyntaxException e) { throw new SAXException(e); } catch (IOException e) { throw new SAXException(e); } catch (TranslationException e) { throw new SAXException(e); } } /** Receive notification of the start of an element. */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { try { start(qName, attributes); } catch (TranslationException e) { throw new SAXException(e); } } /** Receive notification of the end of an element. */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { try { end(qName); } catch (TranslationException e) { throw new SAXException(e); } } /** Receive notification of character data inside an element. */ @Override public void characters(char[] ch, int start, int length) throws SAXException { if (inDTD) { return; } queueText(new String(ch, start, length)); } /** Receive notification of ignorable whitespace in element content. */ @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { if (inDTD) { return; } queueText(new String(ch, start, length)); } /** Receive notification of an XML comment anywhere in the document. */ public void comment(char[] ch, int start, int length) throws SAXException { if (inDTD) { return; } queueComment(new String(ch, start, length)); } /** * Receive notification of an XML processing instruction anywhere in the * document. */ @Override public void processingInstruction(String target, String data) throws SAXException { if (inDTD) { return; } queueProcessingInstruction(target, data); } /** Receive notification of the beginning of the document. */ @Override public void startDocument() throws SAXException { try { mainWriter.write("<?xml version=\"1.0\"?>\n"); } catch (IOException e) { throw new SAXException(e); } entry = new Entry(dialect, this); } /** Receive notification of the end of the document. */ @Override public void endDocument() throws SAXException { try { translateAndFlush(); if (extWriter != null) { extWriter.close(); extWriter = null; } translateAndFlush(); currWriter().close(); } catch (TranslationException e) { throw new SAXException(e); } catch (IOException e) { throw new SAXException(e); } } /** * Report a fatal XML parsing error. Is used to provide feedback. */ @Override public void fatalError(org.xml.sax.SAXParseException e) throws SAXException { try { reportFatalError(e); } catch (MalformedURLException ex) { throw new SAXException(ex); } catch (URISyntaxException ex) { throw new SAXException(ex); } } /** * Report the start of DTD declarations, if any. */ public void startDTD(String name, String publicId, String systemId) throws SAXException { dtd = new DTD(name, publicId, systemId); } /** * Report the end of DTD declarations. Queues the DTD declaration with all * the entities declared. */ public void endDTD() throws SAXException { queueDTD(dtd); inDTD = false; dtd = null; } /** * Report the start of a CDATA section. */ public void startCDATA() throws SAXException { inCDATA = true; } /** * Report the end of a CDATA section. */ public void endCDATA() throws SAXException { inCDATA = false; } /** * Not used: Report the beginning of some internal and external XML * entities. */ public void startEntity(String name) throws SAXException { doStartEntity(name); } /** * Report the end of an entity. * * @param name * The name of the entity that is ending. * @exception SAXException * The application may raise an exception. * @see #startEntity */ public void endEntity(String name) throws SAXException { try { doEndEntity(name); } catch (IOException e) { throw new SAXException(e); } catch (TranslationException e) { throw new SAXException(e); } } /** * Report an internal entity declaration. */ public void internalEntityDecl(String name, String value) throws SAXException { if (inDTD) { return; } Entity entity = new Entity(name, value); internalEntities.put(name, entity); dtd.addEntity(entity); } /** * Report a parsed external entity declaration. */ public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException { if (inDTD) { return; } try { Entity entity = new Entity(name, publicId, localizeSystemId(systemId)); if (isInSource(systemId)) { externalEntities.add(entity); } dtd.addEntity(entity); } catch (MalformedURLException ex) { throw new SAXException(ex); } catch (URISyntaxException ex) { throw new SAXException(ex); } } // ///////////////////////////////////////////////////////////////////////// // unused callbacks // ///////////////////////////////////////////////////////////////////////// /** Not used: An element type declaration. */ public void elementDecl(String name, String model) { } /** Not used: An attribute type declaration. */ public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) { } }