Changeset 1344

Show
Ignore:
Timestamp:
02/07/09 14:04:36 (8 months ago)
Author:
bruno
Message:

whitespace handling changes:

  • don't trim character events anymore, only drop complete-whitespace character events
  • add support for xml:space and t:text

See also docs: 409-kauri
For readability, the template testcases contain code to reformat (pretty-print) the whitespace in the template output. For testcases focussing on whitespace, this should of course not be done, therefore added an option (prettyFormat) to control this.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/universe/kauri-template/src/main/java/org/kauriproject/template/DefaultTemplateBuilder.java

    r1332 r1344  
    1616package org.kauriproject.template; 
    1717 
    18 import java.io.IOException; 
    1918import java.io.InputStream; 
    2019import java.util.ArrayList; 
     
    6463    // TODO: (bug) namespace declarations on KTL directives might be ignored (issue #37) 
    6564    protected NamespaceSupport namespaceSupport; 
     65    /** 
     66     * This stack contains an entry for each nested XML element: either true (whitespace should be stripped), 
     67     * false (whitespace should not be stripped) or null (no change, first non-null value in the stack 
     68     * tells what to do with whitespace). 
     69     */ 
     70    protected List<Boolean> preserveWhitespaceStack; 
    6671 
    6772    protected DocumentBlock docblock; 
     
    100105        this.silent = silent; 
    101106        this.saxParser = createSAXParser(); 
     107        this.preserveWhitespaceStack = new ArrayList<Boolean>(); 
    102108    } 
    103109 
     
    273279            } 
    274280 
     281            if (silencedLevel > 0) { 
     282                silencedLevel++; 
     283                return; 
     284            } 
     285 
    275286            // We only want to take care of the prefix mapping events, and remove the duplicate 
    276287            // info from the xmlns attributes (which may or may not be present, depending on parser settings) 
    277288            attributes = removeXmlnsAttrs(attributes); 
    278289 
     290            // Determine preference for whitespace treatment, if any 
     291            Boolean preserveWhitespace = null; 
     292            if (localName.equals(Directive.TEXT.getTagName()) && uri.equals(NAMESPACE_KTL)) { 
     293                preserveWhitespace = Boolean.TRUE; 
     294            } else if (attributes.getIndex("http://www.w3.org/XML/1998/namespace", "space") != -1) { 
     295                int index = attributes.getIndex("http://www.w3.org/XML/1998/namespace", "space"); 
     296                String space = attributes.getValue(index); 
     297                if (space.equals("preserve")) { 
     298                    preserveWhitespace = Boolean.TRUE; 
     299                } else if (space.equals("default")) { 
     300                    preserveWhitespace = Boolean.FALSE; 
     301                } else { 
     302                    log.warn("Invalid value for xml:space attribute: " + space); 
     303                } 
     304                // Remove xml:space from the attributes 
     305                AttributesImpl newAttrs = new AttributesImpl(attributes); 
     306                newAttrs.removeAttribute(index); 
     307                attributes = newAttrs; 
     308            } 
     309            preserveWhitespaceStack.add(preserveWhitespace); 
     310 
    279311            if (allowedLevel > 0) { 
    280312                allowedLevel++; 
     
    282314 
    283315            int blocksPushed = 1; 
    284             if (silencedLevel > 0) { 
    285                 silencedLevel++; 
    286                 return; 
    287             } 
    288316 
    289317            String bundleName = attributes.getValue(NAMESPACE_I18N, "bundle"); 
     
    434462                                attributes)); 
    435463                        pushBlock(protectblock); 
     464                    } else if (Directive.TEXT == directive) { 
     465                        // don't push a block 
    436466                    } else { 
    437467                        log.warn("Unsupported KTL directive: " + localName); 
     
    489519                ClassCastException { 
    490520 
     521            // revert to previous context 
     522            namespaceSupport.popContext(); 
     523 
    491524            if (silencedLevel > 0) { 
    492525                silencedLevel--; 
     526            } else if (localName.equals(Directive.TEXT.getTagName()) && uri.equals(NAMESPACE_KTL)) { 
     527                // The text instruction does not have a runtime block 
     528                sendCharacters(); 
    493529            } else { 
    494530                sendCharacters(); 
     
    499535                    popBlock(); 
    500536 
    501                 // revert to previous context 
    502                 namespaceSupport.popContext(); 
    503  
    504537                if (allowedLevel > 0) 
    505538                    allowedLevel--; 
     
    508541            } 
    509542 
     543            // Popping the preserve-whitespace-stack should be done after sendCharacters() 
     544            preserveWhitespaceStack.remove(preserveWhitespaceStack.size() - 1); 
    510545        } 
    511546 
    512547        @Override 
    513548        public void characters(char[] ch, int start, int length) throws SAXException { 
    514             // TODO: improve whitespace handling (issue #38) 
    515             String string = String.valueOf(ch).substring(start, start + length).trim(); 
    516             if (string.length() > 0 && silencedLevel == 0) { 
    517                 // text: e.g. HelloWorld 
     549            if (silencedLevel == 0) { 
    518550                // We have to buffer the characters events, because the parser 
    519551                // may split up a single textblock in multiple events. 
     
    525557        private void sendCharacters() { 
    526558            if (charBuffer.length() > 0) { 
    527                 String chars = charBuffer.toString(); 
    528                 TextStep text = new TextStep(elFacade, charLocator, chars.toCharArray(), 0, chars.length()); 
    529                 last.setCompiledNext(text); 
    530                 last = text; 
     559                boolean ws = isWhitespace(charBuffer); 
     560                if (!ws || preserveWhitespace()) { 
     561                    String chars = charBuffer.toString(); 
     562                    TextStep text = new TextStep(elFacade, charLocator, chars.toCharArray(), 0, chars.length()); 
     563                    last.setCompiledNext(text); 
     564                    last = text; 
     565                } 
    531566                charBuffer = new StringBuffer(); 
    532567            } 
     568        } 
     569 
     570        private boolean isWhitespace(StringBuffer buffer) { 
     571            // We follow the same rules as for XSLT to decide if a character is whitespace 
     572            for (int i = 0; i < buffer.length(); i++) { 
     573                char c = buffer.charAt(i); 
     574                if (c != 0x000D && c != 0x000A && c != 0x0009 && c != 0x020) 
     575                    return false; 
     576            } 
     577            return true; 
     578        } 
     579 
     580        private boolean preserveWhitespace() { 
     581            int size = preserveWhitespaceStack.size(); 
     582            for (int i = size - 1; i >= 0; i--) { 
     583                if (preserveWhitespaceStack.get(i) != null) { 
     584                    return preserveWhitespaceStack.get(i); 
     585                } 
     586            } 
     587            // By default, pure-whitespace text between tags is stripped 
     588            return false; 
    533589        } 
    534590 
  • trunk/universe/kauri-template/src/main/java/org/kauriproject/template/Directive.java

    r1108 r1344  
    108108     */ 
    109109    INIT("init"), 
    110     PROTECT("protect"); 
     110    PROTECT("protect"), 
     111    TEXT("text"); 
    111112 
    112113    private final String tagName; 
     
    159160        else if (name.equals(PROTECT.tagName)) 
    160161            return PROTECT; 
     162        else if (name.equals(TEXT.tagName)) 
     163            return TEXT; 
    161164        else 
    162165            return null;// throw RuntimeException ? 
  • trunk/universe/kauri-template/src/test/java/org/kauriproject/template/TemplateExecutionTest.java

    r1005 r1344  
    131131        testFlow("/org/kauriproject/template/init_special.xml", true); 
    132132        // special should yield the same result as flat 
    133         String result1 = loadXML("/org/kauriproject/template/init_flat_result.xml"); 
    134         String result2 = loadXML("/org/kauriproject/template/init_special_result.xml"); 
     133        String result1 = loadXML("/org/kauriproject/template/init_flat_result.xml", true); 
     134        String result2 = loadXML("/org/kauriproject/template/init_special_result.xml", true); 
    135135        assertEquals("Both results should be equal to get a useful test.", result1, result2); 
    136136    } 
     
    156156    } 
    157157 
     158    public void testWhitespace() throws Exception { 
     159        testFlow("/org/kauriproject/template/whitespace.xml", null, true, false); 
     160    } 
     161 
    158162    public void testVariableFromSrc() throws Exception { 
    159163        Map<String, Object> parameters = new HashMap<String, Object>(); 
     
    162166        testFlow("/org/kauriproject/template/variable_from_src.xml", parameters, true); 
    163167    } 
    164      
     168 
    165169    public void testXmlVariables() throws Exception { 
    166170        Map<String, Object> variables = new HashMap<String, Object>(); 
  • trunk/universe/kauri-template/src/test/java/org/kauriproject/template/TemplateServiceTest.java

    r487 r1344  
    4949        // check result 
    5050        if (check) { 
    51             checkResult(template, bos); 
     51            checkResult(template, bos, true); 
    5252        } 
    5353    } 
     
    6060        String template = "/org/kauriproject/template/mix.xml"; 
    6161        if (TEST_DURATION) { 
    62             initTresholds(template); 
     62            initTresholds(template, true); 
    6363        } 
    6464        testService(template, true, flowTreshold); 
     
    6868        String template = "/org/kauriproject/template/big.xml"; 
    6969        if (TEST_DURATION) { 
    70             initTresholds(template); 
     70            initTresholds(template, true); 
    7171        } 
    7272        // test it three times in a row 
  • trunk/universe/kauri-template/src/test/java/org/kauriproject/template/TemplateTestBase.java

    r1004 r1344  
    1717 
    1818import java.io.ByteArrayOutputStream; 
     19import java.io.InputStreamReader; 
     20import java.io.Reader; 
     21import java.io.BufferedReader; 
    1922import java.util.Date; 
    2023import java.util.HashMap; 
     
    6972    } 
    7073 
    71     protected void initTresholds(String template) throws Exception { 
     74    protected void initTresholds(String template, boolean prettyFormat) throws Exception { 
    7275        long start = new Date().getTime(); 
    7376        // load template 
    74         loadXML(template); 
     77        loadXML(template, prettyFormat); 
    7578        long end = new Date().getTime(); 
    7679        // check duration 
     
    150153    } 
    151154 
     155    protected void testFlow(String template, Map<String, Object> variables, boolean check) throws Exception { 
     156        testFlow(template, variables, check, true); 
     157    } 
     158 
    152159    /** 
    153160     * Load, build and execute template and optionally check the result. 
    154161     */ 
    155     protected void testFlow(String template, Map<String, Object> variables, boolean check) throws Exception { 
     162    protected void testFlow(String template, Map<String, Object> variables, boolean check, boolean prettyFormat) 
     163            throws Exception { 
    156164        // init tresholds for duration 
    157165        if (TEST_DURATION) { 
    158             initTresholds(template); 
    159         } 
     166            initTresholds(template, prettyFormat); 
     167        } 
     168 
    160169        // monitor duration 
    161170        long start = new Date().getTime(); 
     171 
    162172        // load and build template 
    163173        CompiledTemplate compiledTemplate = buildTemplate(template, buildTreshold); 
     174 
    164175        // execute template 
    165176        ByteArrayOutputStream outputResult = new ByteArrayOutputStream(); 
    166         TemplateResult result = new TemplateResultImpl(new KauriSaxHandler.NamespaceAsAttributes( 
    167                 new TestHandler(outputResult))); 
     177        TemplateResult result; 
     178        if (prettyFormat) { 
     179            result = new TemplateResultImpl(new KauriSaxHandler.NamespaceAsAttributes( 
     180                    new TestHandler(outputResult))); 
     181        } else { 
     182            result = new TemplateResultImpl(new KauriSaxHandler(outputResult)); 
     183        } 
    168184        executeTemplate(compiledTemplate, result, variables, executionTreshold); 
     185 
    169186        // flush result 
    170187        result.flush(); 
     
    180197        // check result 
    181198        if (check) { 
    182             checkResult(template, outputResult); 
     199            checkResult(template, outputResult, prettyFormat); 
    183200        } 
    184201    } 
     
    187204     * Check if the templateresult meets our expectations. 
    188205     */ 
    189     protected void checkResult(String filename, ByteArrayOutputStream bos) throws Exception { 
     206    protected void checkResult(String filename, ByteArrayOutputStream bos, boolean prettyFormat) throws Exception { 
    190207        String expectedFile = filename.replace(".xml", "_result.xml"); 
    191         String expectedString = loadXML(expectedFile); 
     208        String expectedString = loadXML(expectedFile, prettyFormat); 
    192209        String actualString = bos.toString(ENCODING); 
    193210        if (!expectedString.equals(actualString)) { 
     
    202219    } 
    203220 
    204     protected String loadXML(String filename) throws Exception { 
     221    protected String loadXML(String filename, boolean prettyFormat) throws Exception { 
    205222        Source source = sourceResolver.resolve(filename); 
    206         ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    207         XmlConsumer consumer = new TestHandler(bos); 
    208         XMLReader reader = parser.getXMLReader(); 
    209         reader.setContentHandler(consumer); 
    210         reader.setProperty("http://xml.org/sax/properties/lexical-handler", consumer); 
    211         reader.parse(new InputSource(source.getInputStream())); 
    212         String xmlString = bos.toString(ENCODING); 
    213  
    214         return xmlString; 
     223        if (prettyFormat) { 
     224            ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
     225            XmlConsumer consumer = new TestHandler(bos); 
     226            XMLReader reader = parser.getXMLReader(); 
     227            reader.setContentHandler(consumer); 
     228            reader.setProperty("http://xml.org/sax/properties/lexical-handler", consumer); 
     229            reader.parse(new InputSource(source.getInputStream())); 
     230            String xmlString = bos.toString(ENCODING); 
     231 
     232            return xmlString; 
     233        } else { 
     234            BufferedReader reader = new BufferedReader(new InputStreamReader(source.getInputStream(), "UTF-8")); 
     235            StringBuilder content = new StringBuilder(); 
     236            String line; 
     237            while ((line = reader.readLine()) != null) { 
     238                if (content.length() > 0) 
     239                    content.append("\n"); 
     240                content.append(line); 
     241            } 
     242            reader.close(); 
     243            return content.toString(); 
     244        } 
    215245    } 
    216246