Sunday, January 22, 2012

vkBeautify - XML, JSON or CSS Beautifier

http://www.eslinstructor.net/vkbeautify/

vkBeautify is a small, simple and powerfull javascript plugin to beautify XML, JSON or CSS text.
This script works either as a client-side javascript or as a nodeJS plagin.

Consider to use it if you:
  • create  XML, JSON or CSS  text manually at client side
  • create  XML, JSON or CSS  with tool, but let user to check or edit the result manually
  • get  XML, JSON or CSS  data from web service and check or edit it before or after processing the data
  • other cases when you need to create, edit or check content of an  XML, JSON or CSS  file.
This plugin:
  1. less then 2k if minified
  2. has only one function
  3. takes less then 5 mSec to process 50K text
    ( Celeron 2.2 GHz, 2.0GB, any of 3 major browsers) 

30 comments:

  1. Great piece of code. Just what I needed.
    Try to parse the following. Will end with error.
    The "if" where verify if is a comment "-->" has to verify the right(3) part only not the full string.

    soap:ServerSystem.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.Exception: \n --- End of inner exception stack trace ---\n at blah blah... Int32 nQueryFlags)\n --- End of inner exception stack trace ---

    ReplyDelete
  2. <soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><soap:Fault> <faultcode>soap:Server</faultcode> <faultstring>System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.Exception: \n --- End of inner exception stack trace ---\n at blah blah... Int32 nQueryFlags)\n --- End of inner exception stack trace ---</faultstring>
    <detail/>
    </soap:Fault>
    </soap:Body>
    </soap:Envelope>

    ReplyDelete
  3. Hi Anonymous,

    thank you for using my plugin :)

    Yes, this content is a system message, which does not follow XML syntax rules. String "-->" breaks normal parsing flow. I'm not sure I need to implement additional parsing rules to catch such kind of occasions.
    Do you have any other problems with this plugin? I really appreciate any comments and suggestions.

    thanks again,

    Vadim.

    ReplyDelete
    Replies
    1. I have a hard time to express myself in English, but let's try...
      In fact the sample message that I post it's a SOAP message. By definition a SOAP message it's written using XML.They are used in webservices.
      The code used, finds "the comment" inside de "data" not as a valid xml comment terminator.
      At the samples page, if you change the xml on the basic example from <c>ccc<c> to <c>--><c>, results in a "invalid" indentation plus the "undefined" word. That's because when deep == 0 and the expression "--deep" occurs, it's pointing to -1 and the shift[-1] does not exists.

      Delete
    2. Your English is perfect. But if you speak Russian, we can speak in Russian.
      To the point: I'm not an expert in SOAP syntax. Is the string "-->" legal in SOAP? How often do you see it in the SOAP messages? Does it have any special meaning?

      thanks,

      -Vadim

      Delete
  4. I do not know what happened. I answered your questions a while ago with some details. Now my answer it's smoke. I can't see it. Maybe tomorrow I'll try again.
    About SOAP see it here: http://en.wikipedia.org/wiki/SOAP. It's better than my explanation.
    The main thougth that I've used in my "smoked" answer, it's we don't care if it's legal, how many times we may see it or if has a special meaning. It's part of the data that a particular XML structure contains. We only care that the XML structure follow a set of well-known rules.
    Maybe the previous answer was too long and was deleted. I saw the answer. Maybe it's there and I don't see it. Or because I posted has anonymous and wrote my name at the end.
    Ricardo*P*

    ReplyDelete
  5. Hey Ricardo,

    take a look at github
    https://github.com/vkiryukhin/vkBeautify
    branch "soap". I fixed the "-->" problem for you, but still not sure if I need to commit this fix into master, as in my opinion your web service sends you buggy content. Looks like they simply take error message and insert it into XML text. If they want to use such kind of string they must wrap it with CDATA.

    ReplyDelete
    Replies
    1. Hi,
      I think that it will wise if you commit the fix to the trunk, because you cannot assume that every piece of xml, that your formatter process, it's created following all the rules. Again, the only thing that we may expect it's that the xml structure it's well formed.
      I started using your code on a log viewer web page. The log comes from a system that generates more than 8 million log records on a daily basis. Part of the logged data are completely random huge xml structures persisted as a single unformatted line. As a developer I never know the meaning of the data that those xml structures contains. The only thing I know it's that the line that has to be shown beautified it's a well formatted xml structure that may contain some buggy data.

      Ricardo*P*

      Delete
  6. Thank you for posting this code, Vadim. It is a very welcome complement to Firefox's implementation of the E4X XML(...).toXMLString() function that works in Chrome and Safari and Opera. I am using it with my object-oriented programming class, where we are studying various DOMs.

    One question, though: I see that you use 4 spaces as your indent to show the XML hierarchy. This is of course fine, but I prefer 2. I can easily change the this.step value in function vkbeautify to ' ' to achieve my desired result, but I would prefer that the indent size be a parameter.

    I admit that I am not an expert on the prototype property and structure that you use in your code, but I am having trouble understanding whether I should add the nSpaces parameter (this is what I'd like to call the parameter that controls the indentation size) to the vkbeautify.prototype.xml function definition or to the vkbeautify function itself. I've tried both, with no success.

    Can you plese point me in the right direction here?

    Thank you,
    Jesse

    Jesse M. Heines, Professor
    UMass Lowell Dept. of Computer Science
    http://teaching.cs.uml.edu/~heines
    heines@cs.uml.edu or jesseheines@gmail.com

    ReplyDelete
  7. Dear Dr. Heines.

    First of all I'd like to thank you for using my code for educational purposes.

    To the question:
    There are more than one way to implement the feature you're talking about, viz. to pass a parameter to a public member-function.
    (In the text below I refer to the source code from
    https://github.com/vkiryukhin/vkBeautify/blob/master/vkbeautify.js

    -------------------------

    I. The first way is to use constructor.
    To implement it I need to change only 3 lines:

    // declare constructor with one argument;
    38 function vkbeautify(){ // old
    38 function vkbeautify(step){ // new

    // initialize member variable either with provided argument
    // or (if no argument is provided) with default value;
    40 this.step = ' '; // 4 spaces // old
    40 this.step = step ? step : ' '; // new

    // important: as constructor accepts user's parameter,
    // we can't create function here, in closure. Instead,
    // constructor with argument should be called from
    // user code. So, here, in library code, we make publicly
    // available not this function, but a reference to the function.
    335 window.vkbeautify = new vkbeautify(); // old
    335 window.vkbeautify = vkbeautify; // new

    In user code this function can be used either:
    a) with 2 steps:
    var foo = new vkbeautify(' ');
    var str = foo.xml(some_xml_str);

    b) with one step:
    var str = new vkbeautify(' ').xml(some_xml_str);

    in both cases argument can be ommitted:
    var str = new vkbeautify().xml(some_xml_str);

    I've published this version at:

    https://github.com/vkiryukhin/vkBeautify/blob/step_param/vkbeautify.js

    -------------------------

    II. The second way is to pass "step" parameter as an argument directly
    to the prototyped function, something like this:

    var str = vkbeautify.xml(xml_str, 4);

    Inside member function I check "step" arg and if it's provided I create local array of shifts and use it, otherwise I use member array "this.shift"

    I can't show code here, in this message, as it takes massive code re-writing (pretty simple though). However this version is available at:

    https://github.com/vkiryukhin/vkBeautify/blob/step_param_member/vkbeautify.js

    Both versions are tested very briefly, so please consider them as draft.
    Thought both version are OK, my personal choice is the second one, as from user prospective it looks simpler and more straightforward: like a regular javascript function vs a plugin which should be initialized, configured and which takes time to learn. Maybe I'll merge this version into maser branch. Thank you for this idea :)

    Best regards,

    -Vadim

    ReplyDelete
  8. Wow, Vadim, that's quite a response! How kind of you to take the time to explain all this to me. I sincerely appreciate it.

    It'll take me a little time to look over all this code, but my first reaction for which version is "better" is the same as yours for the same reason: version 2 is "simpler and more straightforward" from an end-coder's point of view.

    I do think that the ability for the end-coder to be able to specify the indent size is very important. I prefer 2 spaces rather than 4 because I tend to have deep hierarchies in my XML files, but I can of course see that others may prefer 3 or 4 for additional distinctiveness with each indent level. Some may even prefer to use a tab, although I never recommend that to my students because the width of a tab is not always 8 spaces.

    For a variety of esoteric reasons, some people may also prefer to use ".." or "...." as the indent prefix. I have done this myself to get the indents to show up in, for example, blog posts that "eat" the indents.

    Thus, for all these reasons, allowing the end-coder to specify the indentation string makes your wonderful code even more flexible.

    Now I have to go read up more on closures... :)

    Yours,
    Jesse

    PS: There is no need to address me as Dr. Heines, please! I don't stand on such formality. :) Even the undergraduates just call me Jesse. BTW, there's another Dr. Heines around, too, and he's there with you in San Francisco. That's my brother! He's "M. Henry Heines," an attorney at Kilpatrick Townsend & Stockton LLP.

    ReplyDelete
  9. Thank you for your idea regarding prefix Jesse.
    Next weekend I plan to work on this plugin: to test it, clean up code and edit documentation (which is the most difficult part of a software development process to me :)
    Please let me know if you have any question about existing or proposed code. I also available via email.

    PS. there is a big chance that your brother and I already seen each other somewhere in Starbucks/Bistro/Sandwich shop/etc., as I work literary in 3 min walk from Embarcadero Center, at Spear St. :))

    ReplyDelete
  10. Hi, Vadim.

    I have 40 students in my class, perhaps half of whom will be looking at your code -- or at least trying to use it in their current assignment -- next week, because the XML(strXmlDoc).toXMLString() ; statement below is only available in Firefox (unfortunately, it appears that blogspot "eats" leading spaces thus destroying indentation, so I've had to use dots):

    .. // http://help.dottoro.com/ljwwrcvr.php
    .. var serializer = new XMLSerializer();
    .. var strXmlDoc = serializer.serializeToString( xmlDoc ) ;
    .. // console.debug( strXmlDoc ) ;

    .. var sPrettyXML ;
    .. try {
    .. .. if ( isFirefox ) { // set by parsing the user agent string
    .. .. .. sPrettyXML = XML(strXmlDoc).toXMLString() ;
    .. .. .. // console.debug( sPrettyXML ) ;
    .. .. }
    .. } catch ( e ) {
    .. .. console.debug( e ) ;
    .. }

    Thus, if you publish the documentation I would be happy to direct them to it and hopefully they will give you some feedback to improve it.

    Here is some additional information on trying to display an XML document created in JavaScript from scratch on a web page using XMLSerializer:

    from https://developer.mozilla.org/en/Parsing_and_serializing_XML:

    .. var oSerializer = new XMLSerializer();
    .. var sPrettyXML = XML(serializer.serializeToString( xmlDoc )).toXMLString();

    The XML and toXMLString functions are part of E4X, an extension of JavaScript that adds direct support for XML. ECMA-357 (E4X) was standardized in June 2004.

    According to http://www.w3schools.com/xml/xml_e4x.asp:

    Firefox is currently the only browser with relatively good support for E4X. There are currently no support for E4X in Opera, Chrome, or Safari. So far there is no indication for of E4X support in Internet Explorer.

    FYI, I have already spoken about our interaction in class, as an illustration of two things:

    (1) The fact that you put your contact information in your code allowed me to contact you in the first place. It amazes me that students turn in code without even their name in it. (Sigh.)

    (2) The fact that I found your code by doing a Google search, but when I had questions about it, I wrote to you and you responded. My wife likes to say about famous people: "They still put their pants on one leg at a time." You and I may not be famous, but most students would never think of writing to the author of code they find on the web to ask questions. I don't know why. Maybe it's just immaturity.

    So, I sincerely appreciate your correspondence. Not only does it help my students learn, but it's helping me learn, too! :)

    Finally, if you would prefer to continue this conversation offline via normal email, that's fine with me, but please just tell me so. I kind of think that followers of your blog may actually be interested in our correspondence, and I am perfectly happy for it to be public. It's your call.

    Jesse

    ReplyDelete
  11. Hi Jesse,

    I've implemented "custom indent" feature for this plugin and merged it into master. the source is available at https://github.com/vkiryukhin/vkBeautify
    and demo page is available at http://www.eslinstructor.net/vkbeautify/
    Please let me know if you or your students have any questions, comments or suggestions regarding this code.

    Thank you,

    -Vadim

    PS. About a way of the conversation: blog is fine with me. It's created exactly for this purpose :)

    ReplyDelete
  12. Thank you, Vadim. I notified my students of this update yesterday. I will certainly let you know if any of them use it and what their experiences are.

    Jesse

    ReplyDelete
  13. Worked brilliantly first time for me...Thanks

    ReplyDelete
  14. Ditto on JumpinDee's comment. It's working perfectly for me, too. Jesse

    ReplyDelete
  15. Vadim,

    Thanks a bunch for writing this library and for making it available as a GPL. Our team works with a lot of JSON and XML, and your library nicely solves for beautifying the outputs. I checked out codemirror and other utilities but yours stood out for its simplicity. It fits nicely and just works! Great job, man, and thanks again!

    Sridhar-

    ReplyDelete
    Replies
    1. Thank you Sridhar,
      it's really nice to get such kind of response :)

      -Vadim

      Delete
  16. Vadim,

    thank you for your efforts creating this library. It has proven very valuable formatting huge SOAP responses without blocking the browser.

    I did find two issues with the recent version and tried to fix them:

    The current xml pretty printer will insert extra lines in front of namespace attributes; you can observe this with your own online demo, just pretty print twice.
    Adding \s* in front of the xmlns\: apparently fixes this:
    .replace(/\s*xmlns\:/g,"~::~xmlns:")
    .replace(/\s*xmlns\=/g,"~::~xmlns=")

    Also, the jsonmin does leave blanks before numbers, after colons and commas. I attempted to fix this, too, e.g.
    .replace(/:\s{0,}(-|\d)/g,':$1')
    when I noticed that you do not parse JSON strings properly with these replacements. You did in fact add the parser solution via JSON.stringify, but not to the .jsonmin version. JSON.stringify(JSON.parse(text)); minifies just fine, so a few extra lines, and you're fine.

    Since the regular expression solution cannot parse JSON strings properly (as in "value": "{}, []" which gets minified but should not), you should forgo the use of regex completely in my opinion. For old browser, including json2.js should not be an issue.

    Anyway, nice job, hope this help.
    - Ludi

    ReplyDelete
  17. Ludi,

    thank you for your review. I think I'll implement both of your suggestions. You're right: as JSON is part of javascript now, it doesn't make sense to keep regexp version any more. And I tested your xmlns solution. It works fine.

    Again, thank you,

    -Vadim

    ReplyDelete
  18. Hi Vadim,

    Great job with this plugin; it makes pretty-printing XML much easier!

    I did notice one small bug in my usage of it, however; it appears that the plugin doesn't properly reduce the indentation after a self-closing XML tag (i.e., a tag like {tag/}, where the {} are XML brackets since this site won't let me use those brackets) when the tag has attributes that cause it to use a second line. The indentation level is increased for the second line, as it should be, but it isn't decreased for the following line, so it ends up with something that looks like this:

    {p}
    ..{html:a
    ....xmlns="http://www.w3.org/1999/xhtml" name="_GoBack"/}
    ....{html:img
    ......xmlns="http://www.w3.org/1999/xhtml" alt="" src="image"/}
    ......{/p}

    Again, the {} are substitutes for the normal XML bracket characters.

    I suspect this behavior is due to the xmlns forcing a new line with an extra level of indentation that isnt subsequently removed by the close of the tag (since a self-closing tag should not change the indentation level).

    Note: I discovered this issue using 0.99.00.beta.

    Thanks for the plugin and keep up the excellent work!

    ReplyDelete
  19. Hi Brett,

    Thank you for letting me know. I'll take a look.

    -Vadim

    ReplyDelete
  20. Hello, Vadim.

    I believe that I have found a bug in vkbeautify.0.99.00.beta.js. Please look at

    http://teaching.cs.uml.edu/~heines/netscout/examples/AjaxXML-v2-forVadim.html

    You will see that this version looks fine. The call to vkbeautify in this version is

    vkbeautify.xml( strXmlDoc, " " ) ;

    Download the source to your own system. (I have added absolute URLs to the <script src="..."> tags so that those links should still work on your system.) Then change the call to vkbeautify to

    vkbeautify.xml( strXmlDoc, " " ) ;

    or

    vkbeautify.xml( strXmlDoc ) ;

    You will see that the opening and closing tags do not align properly, at least not in the tags that are direct children of the root.

    I hope that this is a simple fix. Spacibo za vasha robota. :)

    Yours,
    Jesse

    ~~~~~~~~~~~~~~~~~~~~~~~~~
    Jesse M. Heines, Professor
    UMass Lowell Dept. of Computer Science
    http://teaching.cs.uml.edu/~heines
    mailto:heines@cs.uml.edu

    ReplyDelete
    Replies
    1. A small clarification:

      The blogging software compressed the spaces in the second parameter to vkbeautify.xml. It works fine when the second parameter is two spaces. It does not work fine if the second is either omitted or changed to three or four spaces.

      Jesse

      Delete
    2. Hello, Jesse.

      Thanks for learning Russian :)

      Regarding the bug: I improved XML parsing, so that open and close tags are shown in a new line each, like this one:
      {zzz}
      . . . some text
      {/zzz}

      before this it was inline:
      {zzz} some text {/zzz}

      I also fixed the bug, mentioned by Brett in his comment above. I published updated code on github in umass branch:
      https://github.com/vkiryukhin/vkBeautify/tree/umass
      This code should be carefully tested of course. I tested it very briefly.

      Thank you for using my plugin, Jesse.

      Best regards,

      -Vadim

      Delete
    3. Hi Vadim,

      The new version of the plugin fixed the issue I was having, but I recently discovered another issue that was introduced. Line 94 uses a filter() call to remove any empty elements from the split array, but for whatever reason, Internet Explorer 8 claims that the array doesn't have the method filter() defined, so an exception is thrown. I fixed this locally by replacing the filter() call with a for loop that pushes non-empty elements to a second array, so that may be the best workaround.

      Thanks again for the plugin!

      Delete
  21. This is great! Thanks. Can you change the Sql beautifier to place a newline after the SET keyword in an UPDATE?
    Try this: update mytable set a=1,b=2,c=3 where d=5

    ReplyDelete
    Replies
    1. I should mention I'm using version 0.99 beta.

      Delete
  22. Also... you've got a bug in UNION ALL. Try this:
    select 1 from dual union all select 2 from dual

    ReplyDelete