001 /* 002 * Copyright (c) 2007 Mozilla Foundation 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a 005 * copy of this software and associated documentation files (the "Software"), 006 * to deal in the Software without restriction, including without limitation 007 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 008 * and/or sell copies of the Software, and to permit persons to whom the 009 * Software is furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 020 * DEALINGS IN THE SOFTWARE. 021 */ 022 023 package nu.validator.messages; 024 025 import java.util.SortedSet; 026 027 import org.xml.sax.SAXException; 028 029 import nu.validator.source.SourceHandler; 030 import nu.validator.xml.AttributesImpl; 031 import nu.validator.xml.XhtmlSaxEmitter; 032 033 public class XhtmlSourceHandler implements SourceHandler { 034 035 private static final char[] NEWLINE_SUBSTITUTE = {'\u21A9'}; 036 037 private static final AttributesImpl LINE_BREAK_ATTRS = new AttributesImpl(); 038 039 static { 040 LINE_BREAK_ATTRS.addAttribute("class", "lf"); 041 LINE_BREAK_ATTRS.addAttribute("title", "Line break"); 042 } 043 044 private final AttributesImpl attrs = new AttributesImpl(); 045 046 private final XhtmlSaxEmitter emitter; 047 048 private boolean listOpen; 049 050 private boolean lineOpen; 051 052 private String rangeOpen; 053 054 private boolean charOpen; 055 056 private int lineNumber; 057 058 private boolean lineHadCharacters; 059 060 private SortedSet<Integer> oneBasedLineErrors; 061 062 /** 063 * @param emitter 064 */ 065 public XhtmlSourceHandler(final XhtmlSaxEmitter emitter) { 066 this.emitter = emitter; 067 } 068 069 public void characters(char[] ch, int start, int length) 070 throws SAXException { 071 maybeOpen(); 072 lineHadCharacters = true; 073 emitter.characters(ch, start, length); 074 } 075 076 @SuppressWarnings("boxing") 077 private void maybeOpen() throws SAXException { 078 assert !(lineOpen && !listOpen); 079 if (!listOpen) { 080 emitter.startElementWithClass("ol", "source"); 081 listOpen = true; 082 } 083 if (!lineOpen) { 084 lineNumber++; 085 attrs.clear(); 086 attrs.addAttribute("id", "l" + lineNumber); 087 if (oneBasedLineErrors != null && oneBasedLineErrors.contains(lineNumber)) { 088 attrs.addAttribute("class", "b"); 089 } 090 emitter.startElement("li", attrs); 091 emitter.startElement("code"); 092 lineOpen = true; 093 lineHadCharacters = false; 094 if (rangeOpen != null) { 095 emitter.startElementWithClass("b", rangeOpen); 096 } 097 } 098 } 099 100 public void endCharHilite() throws SAXException { 101 if (!charOpen) { 102 return; 103 } 104 emitter.endElement("b"); 105 charOpen = false; 106 } 107 108 public void endRange() throws SAXException { 109 assert rangeOpen != null; 110 emitter.endElement("b"); 111 rangeOpen = null; 112 } 113 114 public void endSource() throws SAXException { 115 if (charOpen) { 116 endCharHilite(); 117 } 118 assert rangeOpen == null; 119 if (lineOpen) { 120 emitter.endElement("code"); 121 emitter.endElement("li"); 122 } 123 if (listOpen) { 124 emitter.endElement("ol"); 125 } 126 } 127 128 public void newLine() throws SAXException { 129 maybeOpen(); 130 if (charOpen) { 131 endCharHilite(); 132 } 133 if (rangeOpen != null) { 134 emitter.endElement("b"); 135 } 136 emitter.endElement("code"); 137 emitter.startElement("code", LINE_BREAK_ATTRS); 138 emitter.characters(NEWLINE_SUBSTITUTE); 139 emitter.endElement("code"); 140 emitter.endElement("li"); 141 lineOpen = false; 142 } 143 144 public void startCharHilite(int oneBasedLine, int oneBasedColumn) 145 throws SAXException { 146 maybeOpen(); 147 assert !charOpen; 148 assert lineNumber == oneBasedLine; 149 attrs.clear(); 150 attrs.addAttribute("id", "cl" + oneBasedLine + "c" + oneBasedColumn); 151 emitter.startElement("b", attrs); 152 charOpen = true; 153 } 154 155 public void startRange(int oneBasedLine, int oneBasedColumn) 156 throws SAXException { 157 maybeOpen(); 158 assert rangeOpen == null; 159 rangeOpen = "l" + oneBasedLine + "c" + oneBasedColumn; 160 attrs.clear(); 161 attrs.addAttribute("id", rangeOpen); 162 attrs.addAttribute("class", rangeOpen); 163 emitter.startElement("b", attrs); 164 } 165 166 public void startSource(String type, String encoding) throws SAXException { 167 listOpen = false; 168 lineOpen = false; 169 rangeOpen = null; 170 charOpen = false; 171 lineNumber = 0; 172 oneBasedLineErrors = null; 173 } 174 175 public void setLineErrors(SortedSet<Integer> oneBasedLineErrors) throws SAXException { 176 this.oneBasedLineErrors = oneBasedLineErrors; 177 } 178 179 }