@@ -27,7 +27,7 @@ export function appendHtmlElementToHead(host: Tree, htmlFilePath: string, elemen
2727 const headTag = getHtmlHeadTagElement ( htmlContent ) ;
2828
2929 if ( ! headTag ) {
30- throw `Could not find '<head>' element in HTML file: ${ htmlFileBuffer } ` ;
30+ throw Error ( `Could not find '<head>' element in HTML file: ${ htmlFileBuffer } ` ) ;
3131 }
3232
3333 // We always have access to the source code location here because the `getHeadTagElement`
@@ -45,13 +45,54 @@ export function appendHtmlElementToHead(host: Tree, htmlFilePath: string, elemen
4545
4646/** Parses the given HTML file and returns the head element if available. */
4747export function getHtmlHeadTagElement ( htmlContent : string ) : DefaultTreeElement | null {
48+ return getElementByTagName ( 'head' , htmlContent ) ;
49+ }
50+
51+ /** Adds a class to the body of the document. */
52+ export function addBodyClass ( host : Tree , htmlFilePath : string , className : string ) : void {
53+ const htmlFileBuffer = host . read ( htmlFilePath ) ;
54+
55+ if ( ! htmlFileBuffer ) {
56+ throw new SchematicsException ( `Could not read file for path: ${ htmlFilePath } ` ) ;
57+ }
58+
59+ const htmlContent = htmlFileBuffer . toString ( ) ;
60+ const body = getElementByTagName ( 'body' , htmlContent ) ;
61+
62+ if ( ! body ) {
63+ throw Error ( `Could not find <body> element in HTML file: ${ htmlFileBuffer } ` ) ;
64+ }
65+
66+ const classAttribute = body . attrs . find ( attribute => attribute . name === 'class' ) ;
67+
68+ if ( classAttribute ) {
69+ const hasClass = classAttribute . value . split ( ' ' ) . map ( part => part . trim ( ) ) . includes ( className ) ;
70+
71+ if ( ! hasClass ) {
72+ const classAttributeLocation = body . sourceCodeLocation ! . attrs . class ;
73+ const recordedChange = host
74+ . beginUpdate ( htmlFilePath )
75+ . insertRight ( classAttributeLocation . endOffset - 1 , ` ${ className } ` ) ;
76+ host . commitUpdate ( recordedChange ) ;
77+ }
78+ } else {
79+ const recordedChange = host
80+ . beginUpdate ( htmlFilePath )
81+ . insertRight ( body . sourceCodeLocation ! . startTag . endOffset - 1 , ` class="${ className } "` ) ;
82+ host . commitUpdate ( recordedChange ) ;
83+ }
84+ }
85+
86+ /** Finds an element by its tag name. */
87+ function getElementByTagName ( tagName : string , htmlContent : string ) :
88+ DefaultTreeElement | null {
4889 const document = parseHtml ( htmlContent , { sourceCodeLocationInfo : true } ) as DefaultTreeDocument ;
4990 const nodeQueue = [ ...document . childNodes ] ;
5091
5192 while ( nodeQueue . length ) {
5293 const node = nodeQueue . shift ( ) as DefaultTreeElement ;
5394
54- if ( node . nodeName . toLowerCase ( ) === 'head' ) {
95+ if ( node . nodeName . toLowerCase ( ) === tagName ) {
5596 return node ;
5697 } else if ( node . childNodes ) {
5798 nodeQueue . push ( ...node . childNodes ) ;
0 commit comments